commit
101fc6e791
@ -0,0 +1,68 @@ |
|||||||
|
- hosts: all |
||||||
|
gather_facts: no |
||||||
|
serial: "{{ hosts_per_batch | d(1) | int }}" |
||||||
|
strategy: "{{ hosts_strategy | d('linear') }}" |
||||||
|
tasks: |
||||||
|
- name: get primary role |
||||||
|
set_fact: |
||||||
|
host_primary_role: "{%- if primary_role is defined -%}{{ primary_role }}\ |
||||||
|
{%- elif hostvars[inventory_hostname]['primary_role'] is defined -%}{{ hostvars[inventory_hostname]['primary_role'] }}\ |
||||||
|
{%- else -%}{{ inventory_hostname }}\ |
||||||
|
{%- endif -%}" |
||||||
|
|
||||||
|
|
||||||
|
- name: import role mappings |
||||||
|
import_tasks: mappings.yml |
||||||
|
|
||||||
|
|
||||||
|
- name: fail if mappings are missing |
||||||
|
fail: |
||||||
|
msg: role mappings are missing or invalid |
||||||
|
when: (common_roles is not defined) or (common_roles | type_debug != 'list') |
||||||
|
|
||||||
|
|
||||||
|
- name: warn if current role mapping is missing |
||||||
|
debug: |
||||||
|
msg: "mapping for role \"{{ host_primary_role }}\" is missing - using default role mapping at stage 6" |
||||||
|
when: (extra_roles | d({}))[host_primary_role] is not defined |
||||||
|
|
||||||
|
|
||||||
|
- name: build role mapping |
||||||
|
set_fact: |
||||||
|
role_mapping: "{{ (((extra_roles | d({}))[host_primary_role] | d([{ 'stage': 6, 'role': host_primary_role }])) + |
||||||
|
([] if host_primary_role in (no_common_roles | d([])) else common_roles) + |
||||||
|
([{ 'stage': 1, 'role': 'container' }] if 'containers' in group_names else []) + |
||||||
|
([{ 'stage': 3, 'role': 'postgres', 'function': 'integrate' }] if host_primary_role in (database_roles | d([])) else []) |
||||||
|
) | sort(attribute='stage') }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: remember selected stages |
||||||
|
set_fact: |
||||||
|
selected_stages: "{%- if stage is defined and ((stage | string) is search(',')) -%}{{ stage | string | split(',') | list | map('int') | list }}\ |
||||||
|
{%- elif (stage is not defined) or ((stage | int) == 0) -%}{{ [1,2,3,4,5,6,7,8,9] }}\ |
||||||
|
{%- else -%}{{ [stage | int] }}\ |
||||||
|
{%- endif -%}" |
||||||
|
no_log: yes |
||||||
|
|
||||||
|
|
||||||
|
- name: show deployment info |
||||||
|
debug: |
||||||
|
msg: "deploying primary role \"{{ host_primary_role }}\" on host \"{{ inventory_hostname }}\", {{ |
||||||
|
(('stages ' if (selected_stages | length > 1) else 'stage ') ~ (selected_stages | join(', '))) |
||||||
|
if ([1,2,3,4,5,6,7,8,9] | symmetric_difference(selected_stages) | list | length > 0) else 'all stages' }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: run pre_tasks |
||||||
|
include_tasks: tasks/pre_tasks.yml |
||||||
|
|
||||||
|
|
||||||
|
- name: run stages |
||||||
|
include_tasks: tasks/includes/stage.yml |
||||||
|
loop: "{{ selected_stages }}" |
||||||
|
loop_control: |
||||||
|
loop_var: this_stage |
||||||
|
|
||||||
|
|
||||||
|
- name: show deployment info |
||||||
|
debug: |
||||||
|
msg: "ok: deployment completed on host \"{{ inventory_hostname }}\"" |
@ -0,0 +1,13 @@ |
|||||||
|
[defaults] |
||||||
|
interpreter_python = auto_silent |
||||||
|
stdout_callback = debug |
||||||
|
use_persistent_connections = true |
||||||
|
forks = 6 |
||||||
|
internal_poll_interval = 0.01 |
||||||
|
jinja2_native = true |
||||||
|
|
||||||
|
[ssh_connection] |
||||||
|
pipelining = true |
||||||
|
transfer_method = scp |
||||||
|
scp_if_ssh = smart |
||||||
|
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s -o PreferredAuthentications=publickey,password |
@ -0,0 +1,36 @@ |
|||||||
|
ansible_user: root |
||||||
|
ansible_dir: /etc/ansible |
||||||
|
ansible_key_dir: keys |
||||||
|
alpine_version: "3.16" |
||||||
|
|
||||||
|
mac_prefix: 02:FF |
||||||
|
|
||||||
|
default_container_hardware: |
||||||
|
cores: 1 |
||||||
|
cpus: 1 |
||||||
|
cpuunits: 1024 |
||||||
|
memory: 128 |
||||||
|
swap: 128 |
||||||
|
disk: 0.4 |
||||||
|
|
||||||
|
known_external_ca: |
||||||
|
- url: letsencrypt.org |
||||||
|
wildcard: no |
||||||
|
validation_methods: |
||||||
|
- dns-01 |
||||||
|
- url: ';' |
||||||
|
wildcard: yes |
||||||
|
|
||||||
|
bogons: |
||||||
|
- 0.0.0.0/8 |
||||||
|
- 127.0.0.0/8 |
||||||
|
- 169.254.0.0/16 |
||||||
|
- 192.0.0.0/24 |
||||||
|
- 192.0.2.0/24 |
||||||
|
- 198.18.0.0/15 |
||||||
|
- 198.51.100.0/24 |
||||||
|
- 203.0.113.0/24 |
||||||
|
- 240.0.0.0/4 |
||||||
|
|
||||||
|
services: {} |
||||||
|
mail_server: {} |
@ -0,0 +1,126 @@ |
|||||||
|
timezone: Europe/Kirov |
||||||
|
org: Organization Name |
||||||
|
org_localized: Название организации |
||||||
|
tld: org.local |
||||||
|
int_net: 10.0.0.0/8 |
||||||
|
|
||||||
|
int_tld: "corp.{{ tld }}" |
||||||
|
maintainer_email: "admin@{{ tld }}" |
||||||
|
|
||||||
|
timezone_win: Russian Standard Time |
||||||
|
|
||||||
|
container_default_nameserver: 10.40.0.1 |
||||||
|
|
||||||
|
networks: |
||||||
|
srv: |
||||||
|
gw: 10.41.0.1/16 |
||||||
|
tag: 11 |
||||||
|
priv: |
||||||
|
gw: 10.42.0.1/16 |
||||||
|
tag: 12 |
||||||
|
dmz: |
||||||
|
gw: 10.43.0.1/16 |
||||||
|
tag: 13 |
||||||
|
|
||||||
|
|
||||||
|
services: |
||||||
|
db: |
||||||
|
hostname: postgres |
||||||
|
vault: |
||||||
|
hostname: vault |
||||||
|
backup: |
||||||
|
hostname: rest-server |
||||||
|
port: 443 |
||||||
|
internal_ns: |
||||||
|
hostname: ns |
||||||
|
recursive_ns: |
||||||
|
hostname: ns-rec |
||||||
|
filtering_ns: |
||||||
|
- hostname: blocky1 |
||||||
|
- hostname: blocky2 |
||||||
|
acme_dns: |
||||||
|
hostname: acme-dns |
||||||
|
rest_server: |
||||||
|
hostname: rest-server |
||||||
|
mariadb: |
||||||
|
hostname: mariadb |
||||||
|
smb: |
||||||
|
hostname: smb |
||||||
|
|
||||||
|
use_alternative_apk_repo: yes |
||||||
|
|
||||||
|
mail_server: |
||||||
|
tld: "{{ tld }}" |
||||||
|
max_mail_size_bytes: 75000000 |
||||||
|
admin_email: "admin@{{ tld }}" |
||||||
|
|
||||||
|
db_server_hostname: postgres |
||||||
|
db_name: mail |
||||||
|
db_user: mail |
||||||
|
db_pass: pass |
||||||
|
|
||||||
|
mta_hostname: postfix |
||||||
|
mua_hostname: dovecot |
||||||
|
rspamd_hostname: rspamd |
||||||
|
webmail_hostname: mail |
||||||
|
clamav_hostname: clamav |
||||||
|
|
||||||
|
mua_lmtp_port: 11001 |
||||||
|
mua_quota_port: 11002 |
||||||
|
mua_auth_port: 11003 |
||||||
|
mua_managesieve_port: 4190 |
||||||
|
rspamd_port: 11332 |
||||||
|
mta_sts_port: 11000 |
||||||
|
clamav_port: 7357 |
||||||
|
|
||||||
|
mta_actual_hostname: smtp |
||||||
|
mua_actual_hostname: imap |
||||||
|
|
||||||
|
allowed_spf: |
||||||
|
- 1.1.1.1 |
||||||
|
|
||||||
|
domains: |
||||||
|
- "{{ tld }}" |
||||||
|
|
||||||
|
aliases: |
||||||
|
- { source: 'postmaster', source_domain: "{{ tld }}", target: 'admin', target_domain: "{{ tld }}" } |
||||||
|
- { source: 'hostmaster', source_domain: "{{ tld }}", target: 'admin', target_domain: "{{ tld }}" } |
||||||
|
- { source: 'webmaster', source_domain: "{{ tld }}", target: 'admin', target_domain: "{{ tld }}" } |
||||||
|
- { source: 'abuse', source_domain: "{{ tld }}", target: 'admin', target_domain: "{{ tld }}" } |
||||||
|
- { source: 'caa-report', source_domain: "{{ tld }}", target: 'admin', target_domain: "{{ tld }}" } |
||||||
|
- { source: 'dkim-report', source_domain: "{{ tld }}", target: 'admin', target_domain: "{{ tld }}" } |
||||||
|
- { source: 'dmarc-report', source_domain: "{{ tld }}", target: 'admin', target_domain: "{{ tld }}" } |
||||||
|
- { source: 'smtp-tls-report', source_domain: "{{ tld }}", target: 'admin', target_domain: "{{ tld }}" } |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
acme_preferred_chain: ISRG Root X1 |
||||||
|
|
||||||
|
winrm_remote_user: remote-admin |
||||||
|
winrm_bootstrap_password: bootstrap123 |
||||||
|
|
||||||
|
|
||||||
|
backup_filters: |
||||||
|
none: |
||||||
|
- "*" |
||||||
|
- "!*/" |
||||||
|
|
||||||
|
office: |
||||||
|
- "!*.doc" |
||||||
|
- "!*.docx" |
||||||
|
- "!*.xls" |
||||||
|
- "!*.xlsx" |
||||||
|
- "!*.ppt" |
||||||
|
- "!*.pptx" |
||||||
|
- "!*.txt" |
||||||
|
- "!*.ods" |
||||||
|
- "!*.odt" |
||||||
|
- "!*.odp" |
||||||
|
- "!*.pdf" |
||||||
|
|
||||||
|
images: |
||||||
|
- "!*.jpg" |
||||||
|
- "!*.jpeg" |
||||||
|
- "!*.png" |
||||||
|
- "!*.tiff" |
@ -0,0 +1,10 @@ |
|||||||
|
is_windows: true |
||||||
|
|
||||||
|
ansible_connection: winrm |
||||||
|
ansible_user: "{{ winrm_remote_user }}" |
||||||
|
|
||||||
|
ansible_winrm_transport: credssp |
||||||
|
ansible_winrm_scheme: http |
||||||
|
ansible_port: 5985 |
||||||
|
|
||||||
|
primary_role: workstation |
@ -0,0 +1,36 @@ |
|||||||
|
all: |
||||||
|
children: |
||||||
|
containers: |
||||||
|
hosts: |
||||||
|
ansible: |
||||||
|
ansible_host: 10.0.0.3 |
||||||
|
ansible_ssh_private_key_file: /etc/ansible/keys/ansible |
||||||
|
container_password: --- |
||||||
|
container_id: 100 |
||||||
|
container_network: srv |
||||||
|
database: {user: 'test', name: 'test', pass: 'test'} |
||||||
|
|
||||||
|
|
||||||
|
nodes: |
||||||
|
hosts: |
||||||
|
node1: |
||||||
|
ansible_host: 10.0.0.2 |
||||||
|
ansible_password: --- |
||||||
|
ansible_ssh_extra_args: -o StrictHostKeyChecking=no |
||||||
|
external_ipv4: 1.1.1.1 |
||||||
|
primary_role: proxmox |
||||||
|
container_mtu: 1390 |
||||||
|
|
||||||
|
|
||||||
|
windows: |
||||||
|
children: |
||||||
|
workstations: |
||||||
|
|
||||||
|
|
||||||
|
infra: |
||||||
|
vars: |
||||||
|
ansible_group_priority: 1000 |
||||||
|
children: |
||||||
|
containers: |
||||||
|
nodes: |
||||||
|
windows: |
@ -0,0 +1,138 @@ |
|||||||
|
- name: define role list |
||||||
|
set_fact: |
||||||
|
# common roles for all primary roles |
||||||
|
common_roles: |
||||||
|
- {stage: 2, role: 'common'} |
||||||
|
- {stage: 3, role: 'ns', function: 'add_records'} |
||||||
|
- {stage: 5, role: 'mail-user'} |
||||||
|
- {stage: 8, role: 'iptables'} |
||||||
|
- {stage: 9, role: 'backup', function: 'setup'} |
||||||
|
|
||||||
|
# these primary roles do not inherit common roles |
||||||
|
no_common_roles: |
||||||
|
- mikrotik |
||||||
|
- workstation |
||||||
|
|
||||||
|
# these primary roles will always inherit postgres integration |
||||||
|
database_roles: |
||||||
|
- acme-dns |
||||||
|
- asterisk |
||||||
|
- gitea |
||||||
|
- roundcube |
||||||
|
- shop |
||||||
|
- wikijs |
||||||
|
- vault |
||||||
|
|
||||||
|
# additional roles for specific primary roles |
||||||
|
extra_roles: |
||||||
|
ca: |
||||||
|
- {stage: 2, role: 'ca', function: 'install'} |
||||||
|
coredns: |
||||||
|
- {stage: 2, role: 'coredns', function: 'install'} |
||||||
|
- {stage: 4, role: 'coredns', function: 'install_tls'} |
||||||
|
mariadb: |
||||||
|
- {stage: 4, role: 'mariadb', function: 'install'} |
||||||
|
mikrotik: |
||||||
|
- {stage: 3, role: 'ns', function: 'add_records'} |
||||||
|
- {stage: 5, role: 'mikrotik'} |
||||||
|
nsd: |
||||||
|
- {stage: 4, role: 'nsd', function: 'install'} |
||||||
|
- {stage: 4, role: 'nsd', function: 'populate'} |
||||||
|
- {stage: 5, role: 'nsd', function: 'install_dnssec'} |
||||||
|
- {stage: 5, role: 'nsd', function: 'install_tls'} |
||||||
|
postfix: |
||||||
|
- {stage: 3, role: 'mail-db'} |
||||||
|
- {stage: 4, role: 'postfix'} |
||||||
|
postgres: |
||||||
|
- {stage: 2, role: 'postgres', function: 'install'} |
||||||
|
- {stage: 3, role: 'postgres', function: 'install_tls'} |
||||||
|
powerdns: |
||||||
|
- {stage: 2, role: 'postgres', function: 'integrate'} |
||||||
|
- {stage: 2, role: 'powerdns', function: 'install'} |
||||||
|
- {stage: 3, role: 'ca', function: 'certs'} |
||||||
|
proxmox: |
||||||
|
- {stage: 1, role: 'common'} |
||||||
|
- {stage: 1, role: 'proxmox', function: 'install'} |
||||||
|
- {stage: 5, role: 'mail-user'} |
||||||
|
- {stage: 5, role: 'proxmox', function: 'tls'} |
||||||
|
- {stage: 6, role: 'proxmox', function: 'mail'} |
||||||
|
rest-server: |
||||||
|
- {stage: 6, role: 'rest-server', function: 'install'} |
||||||
|
workstation: |
||||||
|
- {stage: 3, role: 'ns', function: 'add_records'} |
||||||
|
- {stage: 5, role: 'workstation'} |
||||||
|
|
||||||
|
# recommended hardware parameters for each primary role |
||||||
|
role_hardware: |
||||||
|
acme-dns: {cores: 2, memory: 96, swap: 64, disk: 0.15} |
||||||
|
ansible: {cores: 4, memory: 256, swap: 384, disk: 1.5} |
||||||
|
asterisk: {cores: 4, memory: 192, swap: 96, disk: 0.6, cpuunits: 2048} |
||||||
|
blocky: {cores: 4, memory: 384, swap: 128, disk: 0.15} |
||||||
|
ca: {cores: 2, memory: 128, swap: 64, disk: 0.15, cpuunits: 512} |
||||||
|
clamav: {cores: 4, memory: 2048, swap: 256, disk: 0.75} |
||||||
|
coredns: {cores: 4, memory: 128, swap: 64, disk: 0.15} |
||||||
|
crl: {cores: 2, memory: 128, swap: 48, disk: 0.15} |
||||||
|
dovecot: {cores: 4, memory: 256, swap: 64, disk: 0.15} |
||||||
|
gitea: {cores: 4, memory: 512, swap: 256, disk: 1} |
||||||
|
grafana: {cores: 4, memory: 512, swap: 256, disk: 0.4} |
||||||
|
mariadb: {cores: 4, memory: 256, swap: 128, disk: 0.4} |
||||||
|
mc: {cores: 4, memory: 2048, swap: 512, disk: 0.5} |
||||||
|
nsd: {cores: 2, memory: 256, swap: 256, disk: 0.15} |
||||||
|
ntp: {cores: 2, memory: 64, swap: 128, disk: 0.15} |
||||||
|
postfix: {cores: 4, memory: 256, swap: 48, disk: 0.15} |
||||||
|
postgres: {cores: 4, memory: 256, swap: 256, disk: 0.5} |
||||||
|
powerdns: {cores: 2, memory: 96, swap: 64, disk: 0.15} |
||||||
|
prometheus: {cores: 4, memory: 512, swap: 256, disk: 0.3} |
||||||
|
rclone: {cores: 4, memory: 192, swap: 96, disk: 0.2, cpuunits: 768} |
||||||
|
rest-server: {cores: 4, memory: 256, swap: 192, disk: 0.2, cpuunits: 512} |
||||||
|
roundcube: {cores: 4, memory: 384, swap: 256, disk: 0.5} |
||||||
|
rspamd: {cores: 4, memory: 768, swap: 128, disk: 0.3} |
||||||
|
seafile: {cores: 4, memory: 1024, swap: 1024, disk: 5} |
||||||
|
shop: {cores: 4, memory: 192, swap: 128, disk: 0.4} |
||||||
|
smb: {cores: 2, memory: 128, swap: 64, disk: 0.15} |
||||||
|
strongswan: {cores: 4, memory: 128, swap: 48, disk: 0.15} |
||||||
|
unbound: {cores: 2, memory: 128, swap: 64, disk: 0.15} |
||||||
|
uptime-kuma: {cores: 4, memory: 384, swap: 128, disk: 0.5} |
||||||
|
vault: {cores: 4, memory: 128, swap: 64, disk: 0.3} |
||||||
|
web: {cores: 4, memory: 128, swap: 64, disk: 0.2} |
||||||
|
wikijs: {cores: 4, memory: 256, swap: 256, disk: 0.75} |
||||||
|
|
||||||
|
# role dependency table |
||||||
|
# 0 - DNS ok |
||||||
|
# 1 - DB ok |
||||||
|
role_dependency: |
||||||
|
acme-dns: 0 |
||||||
|
ansible: 0 |
||||||
|
asterisk: 2 |
||||||
|
blocky: 0 |
||||||
|
ca: 0 |
||||||
|
clamav: 1 |
||||||
|
coredns: 0 |
||||||
|
crl: 1 |
||||||
|
dovecot: 2 |
||||||
|
gitea: 2 |
||||||
|
grafana: 2 |
||||||
|
mariadb: 0 |
||||||
|
mc: 3 |
||||||
|
nsd: 0 |
||||||
|
ntp: 0 |
||||||
|
postfix: 2 |
||||||
|
postgres: 0 |
||||||
|
powerdns: 1 |
||||||
|
prometheus: 1 |
||||||
|
rclone: 1 |
||||||
|
rest-server: 0 |
||||||
|
roundcube: 2 |
||||||
|
rspamd: 2 |
||||||
|
seafile: 3 |
||||||
|
shop: 2 |
||||||
|
smb: 1 |
||||||
|
strongswan: 1 |
||||||
|
unbound: 0 |
||||||
|
uptime-kuma: 3 |
||||||
|
vault: 2 |
||||||
|
web: 1 |
||||||
|
wikijs: 3 |
||||||
|
|
||||||
|
run_once: yes |
||||||
|
no_log: yes |
@ -0,0 +1,44 @@ |
|||||||
|
acme_dns_user: acmedns |
||||||
|
acme_dns_group: acmedns |
||||||
|
acme_dns_dir: /opt/acmedns |
||||||
|
|
||||||
|
acme_dns_tld: "acme-dns.{{ acme_tld | d(tld) }}" |
||||||
|
acme_dns_ns: "ns.acme-dns.{{ acme_tld | d(tld) }}" |
||||||
|
acme_dns_admin: "{{ maintainer_email | d('admin@' ~ (acme_tld | d(tld))) }}" |
||||||
|
|
||||||
|
acme_dns_api_port: 8080 |
||||||
|
|
||||||
|
|
||||||
|
acme_dns_default_config: |
||||||
|
general: |
||||||
|
listen: ":53" |
||||||
|
protocol: both4 |
||||||
|
domain: "{{ acme_dns_tld }}" |
||||||
|
nsname: "{{ acme_dns_ns | d(acme_dns_tld) }}" |
||||||
|
nsadmin: "{{ acme_dns_admin | replace('@', '.') }}" |
||||||
|
|
||||||
|
records: |
||||||
|
- "{{ acme_dns_tld ~ '. A ' ~ acme_dns_external_ipv4 }}" |
||||||
|
- "{{ (acme_dns_ns | d(acme_dns_tld)) ~ '. A ' ~ acme_dns_external_ipv4 }}" |
||||||
|
- "{{ acme_dns_tld ~ '. NS ' ~ (acme_dns_ns | d(acme_dns_tld)) ~ '.' }}" |
||||||
|
|
||||||
|
database: |
||||||
|
engine: postgres |
||||||
|
connection: "{{ 'postgresql://' ~ database_user ~ ':' ~ database_pass ~ '@' ~ database_host ~ '/' ~ database_name ~ '?sslmode=disable' }}" |
||||||
|
|
||||||
|
api: |
||||||
|
ip: "0.0.0.0" |
||||||
|
autocert_port: 80 |
||||||
|
port: "{{ acme_dns_api_port }}" |
||||||
|
disable_registration: no |
||||||
|
tls: none |
||||||
|
use_header: no |
||||||
|
|
||||||
|
notification_email: "{{ letsencrypt_email | d(maintainer_email) }}" |
||||||
|
corsorigins: |
||||||
|
- "*" |
||||||
|
|
||||||
|
logconfig: |
||||||
|
loglevel: debug |
||||||
|
logtype: stdout |
||||||
|
logformat: text |
@ -0,0 +1,5 @@ |
|||||||
|
- name: restart acme-dns |
||||||
|
service: |
||||||
|
name: acme-dns |
||||||
|
state: restarted |
||||||
|
|
@ -0,0 +1,113 @@ |
|||||||
|
- name: set acme_dns_cfg |
||||||
|
set_fact: |
||||||
|
acme_dns_cfg: "{{ acme_dns_default_config | d({}) | combine(acme_dns_config | d({}), recursive=true) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: install dependencies |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- libcap |
||||||
|
|
||||||
|
|
||||||
|
- name: create user and group |
||||||
|
include_tasks: tasks/create_user.yml |
||||||
|
vars: |
||||||
|
user: |
||||||
|
name: "{{ acme_dns_user }}" |
||||||
|
group: "{{ acme_dns_group }}" |
||||||
|
dir: "{{ acme_dns_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: get and extract latest version of acme-dns |
||||||
|
include_tasks: tasks/get_lastversion.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
name: fritterhoff/acme-dns |
||||||
|
location: github |
||||||
|
assets: yes |
||||||
|
asset_filter: 'Linux_amd64.tar.gz$' |
||||||
|
file: "{{ acme_dns_dir }}/last_version" |
||||||
|
extract: "{{ acme_dns_dir }}" |
||||||
|
user: "{{ acme_dns_user }}" |
||||||
|
group: "{{ acme_dns_group }}" |
||||||
|
notify: restart acme-dns |
||||||
|
|
||||||
|
|
||||||
|
- name: delete unnecessary files |
||||||
|
file: |
||||||
|
path: "{{ acme_dns_dir }}/{{ item }}" |
||||||
|
state: absent |
||||||
|
loop: |
||||||
|
- CHANGELOG.md |
||||||
|
- LICENSE |
||||||
|
- README.md |
||||||
|
|
||||||
|
|
||||||
|
- name: template acme-dns config |
||||||
|
template: |
||||||
|
src: config.j2 |
||||||
|
dest: "{{ acme_dns_dir }}/config.cfg" |
||||||
|
force: yes |
||||||
|
mode: 0400 |
||||||
|
owner: "{{ acme_dns_user }}" |
||||||
|
group: "{{ acme_dns_group }}" |
||||||
|
lstrip_blocks: yes |
||||||
|
notify: restart acme-dns |
||||||
|
|
||||||
|
|
||||||
|
- name: template init script |
||||||
|
template: |
||||||
|
src: init.j2 |
||||||
|
dest: /etc/init.d/acme-dns |
||||||
|
force: yes |
||||||
|
mode: "+x" |
||||||
|
notify: restart acme-dns |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure acme-dns binary has executable bit set |
||||||
|
file: |
||||||
|
path: "{{ acme_dns_dir }}/acme-dns" |
||||||
|
mode: "+x" |
||||||
|
|
||||||
|
|
||||||
|
- name: add cap_net_bind_service to acme-dns executable |
||||||
|
community.general.capabilities: |
||||||
|
path: "{{ acme_dns_dir }}/acme-dns" |
||||||
|
capability: cap_net_bind_service+ep |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
|
||||||
|
- name: set acme server address |
||||||
|
set_fact: |
||||||
|
acme_server: "http://127.0.0.1:{{ acme_dns_api_port }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: install and configure nginx |
||||||
|
include_role: |
||||||
|
name: nginx |
||||||
|
vars: |
||||||
|
nginx: |
||||||
|
servers: |
||||||
|
- conf: nginx_server |
||||||
|
certs: "{{ host_tls }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ acme_dns_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: enable and start acme-dns |
||||||
|
service: |
||||||
|
name: acme-dns |
||||||
|
state: started |
||||||
|
enabled: yes |
@ -0,0 +1,26 @@ |
|||||||
|
{% macro acme_dns_option(option) -%} |
||||||
|
{% if option.value is boolean -%} |
||||||
|
{{ option.key }} = {{ 'true' if option.value else 'false' }} |
||||||
|
{% elif option.value | type_debug == 'list' -%} |
||||||
|
{{ option.key }} = [ |
||||||
|
{%- for s in option.value -%} |
||||||
|
"{{- s -}}", |
||||||
|
{%- endfor -%} |
||||||
|
] |
||||||
|
{% elif option.value != None -%} |
||||||
|
{{ option.key }} = "{{ option.value }}" |
||||||
|
{% endif -%} |
||||||
|
{% endmacro -%} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% for section in (acme_dns_cfg | d({}) | dict2items) -%} |
||||||
|
[{{ section.key | lower }}] |
||||||
|
{% for option in (section.value | d({}) | dict2items) -%} |
||||||
|
{{ acme_dns_option(option) -}} |
||||||
|
{% endfor %} |
||||||
|
|
||||||
|
{%- if not loop.last %} |
||||||
|
|
||||||
|
{% endif -%} |
||||||
|
{% endfor %} |
@ -0,0 +1,18 @@ |
|||||||
|
#!/sbin/openrc-run |
||||||
|
|
||||||
|
name="$SVCNAME" |
||||||
|
command="{{ acme_dns_dir }}/$SVCNAME" |
||||||
|
directory="{{ acme_dns_dir }}" |
||||||
|
command_user="{{ acme_dns_user }}:{{ acme_dns_group }}" |
||||||
|
pidfile="/var/run/$SVCNAME.pid" |
||||||
|
command_background=true |
||||||
|
start_stop_daemon_args="--stdout-logger logger --stderr-logger logger" |
||||||
|
|
||||||
|
depend() { |
||||||
|
need net |
||||||
|
use dns |
||||||
|
} |
||||||
|
|
||||||
|
start_pre() { |
||||||
|
setcap 'cap_net_bind_service=+ep' {{ acme_dns_dir }}/$SVCNAME |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
location / { |
||||||
|
proxy_pass http://127.0.0.1:{{ acme_dns_api_port }}; |
||||||
|
proxy_http_version 1.1; |
||||||
|
proxy_set_header Host $host; |
||||||
|
proxy_set_header X-Real-IP $remote_addr; |
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||||
|
proxy_set_header X-Forwarded-Proto $scheme; |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
acme_directory: /etc/letsencrypt |
||||||
|
acme_max_log_backups: 5 |
@ -0,0 +1,202 @@ |
|||||||
|
- name: install certbot |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: certbot |
||||||
|
|
||||||
|
|
||||||
|
- name: create certbot directories |
||||||
|
file: |
||||||
|
path: "{{ item }}" |
||||||
|
state: directory |
||||||
|
loop: |
||||||
|
- "{{ acme_directory }}" |
||||||
|
- "{{ acme_directory }}/cron" |
||||||
|
|
||||||
|
|
||||||
|
- name: change certbot directory permissions |
||||||
|
file: |
||||||
|
path: "{{ acme_directory ~ '/' ~ item }}" |
||||||
|
state: directory |
||||||
|
mode: "g+rx,o+rx" |
||||||
|
loop: |
||||||
|
- archive |
||||||
|
- live |
||||||
|
|
||||||
|
|
||||||
|
- name: check if acme-dns auth hook already exists |
||||||
|
stat: |
||||||
|
path: "{{ acme_directory }}/acme-dns-auth.py" |
||||||
|
register: result |
||||||
|
|
||||||
|
|
||||||
|
- name: download acme-dns auth hook |
||||||
|
get_url: |
||||||
|
url: "https://raw.githubusercontent.com/RangeForce/acme-dns-certbot-joohoi/master/acme-dns-auth.py" |
||||||
|
dest: "{{ acme_directory }}/acme-dns-auth.py" |
||||||
|
force: no |
||||||
|
mode: "+x" |
||||||
|
when: result.stat.exists == false |
||||||
|
|
||||||
|
|
||||||
|
- name: update python interpreter in acme-dns-auth to python3 |
||||||
|
lineinfile: |
||||||
|
path: "{{ acme_directory }}/acme-dns-auth.py" |
||||||
|
regexp: '^#!\/usr\/bin\/env python\s*$' |
||||||
|
line: '#!/usr/bin/env python3' |
||||||
|
|
||||||
|
|
||||||
|
- name: clear acme fqdn list |
||||||
|
set_fact: |
||||||
|
acme_domain_list: "{{ [] }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: build acme fqdn list |
||||||
|
set_fact: |
||||||
|
acme_domain_list: "{{ (acme_domain_list | d([])) + |
||||||
|
([item.fqdn | d((item.hostname | d(host_name)) ~ '.' ~ (item.tld | d(host_tld)))] if item is mapping else |
||||||
|
[item]) }}" |
||||||
|
loop: "{{ acme_hosts if (acme_hosts | type_debug == 'list') else [] }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: build single acme fqdn |
||||||
|
set_fact: |
||||||
|
acme_domain_list: "{%- if acme_fqdn is defined and acme_fqdn != None -%}\ |
||||||
|
{{ [ acme_fqdn ] }}\ |
||||||
|
{%- elif (acme_hostname is defined and acme_hostname != None) or (acme_tld is defined and acme_tld != None) -%}\ |
||||||
|
{{ [((acme_hostname is defined and acme_hostname != None) | ternary(acme_hostname, host_name)) ~ '.' ~ |
||||||
|
((acme_tld is defined and acme_tld != None) | ternary(acme_tld, host_tld))] }}\ |
||||||
|
{%- else -%}\ |
||||||
|
{{ [ host_fqdn ] }}\ |
||||||
|
{%- endif -%}" |
||||||
|
when: (acme_hosts is not defined) or (acme_hosts | type_debug != 'list') |
||||||
|
|
||||||
|
|
||||||
|
- name: set acme parameters |
||||||
|
set_fact: |
||||||
|
acme_cert_name: "{{ acme_id if (acme_id is defined) and (acme_id != None) else (host_name ~ ('-ecc' if (acme_ecc | d(false) == true) else '')) }}" |
||||||
|
acme_target_server: "{%- if (acme_server is defined) and (acme_server != None) -%}\ |
||||||
|
{{ acme_server }}\ |
||||||
|
{%- else -%}\ |
||||||
|
{{ (services.acme_dns.protocol | d('https')) ~ '://' ~ services.acme_dns.hostname ~ '.' ~ (services.acme_dns.tld | d(int_tld)) ~ |
||||||
|
((':' ~ services.acme_dns.port) if services.acme_dns.port is defined else '') }}\ |
||||||
|
{%- endif -%}" |
||||||
|
|
||||||
|
|
||||||
|
- name: set certbot parameters |
||||||
|
set_fact: |
||||||
|
acme_params: "{{ ['--manual', |
||||||
|
'--manual-auth-hook ' ~ ((acme_directory ~ '/acme-dns-auth.py') | quote), |
||||||
|
'--preferred-challenges dns', |
||||||
|
'--debug-challenges', |
||||||
|
('--key-type ecdsa' if (acme_ecc | d(false) == true) else ''), |
||||||
|
('--staging' if (acme_staging | d(false) == true) else ''), |
||||||
|
('--force-renewal' if (acme_force | d(false) == true) else ''), |
||||||
|
('--must-staple' if (acme_stapling | d(false) == true) else ''), |
||||||
|
'--cert-name ' ~ (acme_cert_name | quote), |
||||||
|
'--non-interactive', |
||||||
|
'--agree-tos', |
||||||
|
'--email ' ~ ((acme_email | d(maintainer_email)) | quote), |
||||||
|
'--no-eff-email', |
||||||
|
(('--preferred-chain ' ~ (acme_preferred_chain | quote)) if acme_preferred_chain is defined else ''), |
||||||
|
'--max-log-backups ' ~ (acme_max_log_backups | quote) |
||||||
|
] | select() | list | join(' ') }}" |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: issue cert with dns mode |
||||||
|
shell: |
||||||
|
cmd: "certbot certonly {{ acme_params }} -d {{ acme_domain_list | map('quote') | join(' -d ') }}" |
||||||
|
chdir: /usr/bin |
||||||
|
environment: |
||||||
|
ACMEDNS_URL: "{{ acme_target_server }}" |
||||||
|
register: result |
||||||
|
changed_when: ('Successfully received certificate' in result.stdout) |
||||||
|
notify: "{{ acme_notify if (acme_notify is defined) and (acme_notify != None) else omit }}" |
||||||
|
|
||||||
|
rescue: |
||||||
|
- name: wait for user interaction (CNAME record must be set manually) |
||||||
|
pause: |
||||||
|
prompt: "{{ result.stdout }}" |
||||||
|
|
||||||
|
- name: try again to issue cert with dns mode |
||||||
|
shell: |
||||||
|
cmd: "certbot certonly {{ acme_params }} -d {{ acme_domain_list | map('quote') | join(' -d ') }}" |
||||||
|
chdir: /usr/bin |
||||||
|
environment: |
||||||
|
ACMEDNS_URL: "{{ acme_target_server }}" |
||||||
|
register: result |
||||||
|
changed_when: ('Successfully received certificate' in result.stdout) |
||||||
|
notify: "{{ acme_notify if (acme_notify is defined) and (acme_notify != None) else omit }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: create symlinks |
||||||
|
file: |
||||||
|
path: "{{ item.dest }}" |
||||||
|
src: "{{ acme_directory ~ '/live/' ~ acme_cert_name ~ '/' ~ item.src }}" |
||||||
|
state: link |
||||||
|
force: yes |
||||||
|
when: (item.dest is string) and (item.dest | length > 0) and (acme_use_symlinks | d(true) == true) |
||||||
|
loop: |
||||||
|
- { src: 'fullchain.pem', dest: "{{ acme_cert | d(None) }}" } |
||||||
|
- { src: 'privkey.pem', dest: "{{ acme_key | d(None) }}" } |
||||||
|
- { src: 'cert.pem', dest: "{{ acme_cert_single | d(None) }}" } |
||||||
|
- { src: 'chain.pem', dest: "{{ acme_chain | d(None) }}" } |
||||||
|
notify: "{{ acme_notify if (acme_notify is defined) and (acme_notify != None) else omit }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: fix ownership on archive dir |
||||||
|
file: |
||||||
|
path: "{{ acme_directory ~ '/archive/' ~ acme_cert_name }}" |
||||||
|
follow: no |
||||||
|
recurse: yes |
||||||
|
owner: "{{ acme_owner if (acme_owner is defined) and (acme_owner != None) else omit }}" |
||||||
|
group: "{{ acme_group if (acme_group is defined) and (acme_group != None) else omit }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: copy certs |
||||||
|
copy: |
||||||
|
src: "{{ acme_directory ~ '/live/' ~ acme_cert_name ~ '/' ~ item.src }}" |
||||||
|
dest: "{{ item.dest }}" |
||||||
|
remote_src: yes |
||||||
|
mode: 0600 |
||||||
|
owner: "{{ acme_owner if (acme_owner is defined) and (acme_owner != None) else omit }}" |
||||||
|
group: "{{ acme_group if (acme_group is defined) and (acme_group != None) else omit }}" |
||||||
|
when: (item.dest is string) and (item.dest | length > 0) and (acme_use_symlinks | d(true) == false) |
||||||
|
loop: |
||||||
|
- { src: 'fullchain.pem', dest: "{{ acme_cert | d(None) }}" } |
||||||
|
- { src: 'privkey.pem', dest: "{{ acme_key | d(None) }}" } |
||||||
|
- { src: 'cert.pem', dest: "{{ acme_cert_single | d(None) }}" } |
||||||
|
- { src: 'chain.pem', dest: "{{ acme_chain | d(None) }}" } |
||||||
|
notify: "{{ acme_notify | d(omit) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: edit renewal file |
||||||
|
lineinfile: |
||||||
|
path: "{{ acme_directory ~ '/renewal/' ~ acme_cert_name ~ '.conf' }}" |
||||||
|
regexp: '^{{ item.name | regex_escape }}(\s+)=' |
||||||
|
line: '{{ item.name }} = {{ item.value }}' |
||||||
|
insertafter: '^\[renewalparams\]' |
||||||
|
create: no |
||||||
|
firstmatch: yes |
||||||
|
when: (item.value is string) and (item.value | length > 0) and |
||||||
|
((item.extra_condition is not defined) or (item.extra_condition | d(true))) |
||||||
|
loop: |
||||||
|
- { name: 'renew_hook', value: "{{ acme_directory ~ '/cron/' ~ acme_cert_name ~ '.sh' }}" } |
||||||
|
|
||||||
|
|
||||||
|
- name: create custom renewal hook file |
||||||
|
template: |
||||||
|
src: renewal.j2 |
||||||
|
dest: "{{ acme_directory ~ '/cron/' ~ acme_cert_name ~ '.sh' }}" |
||||||
|
force: yes |
||||||
|
mode: 0500 |
||||||
|
lstrip_blocks: yes |
||||||
|
|
||||||
|
|
||||||
|
- name: add certbot to crontab |
||||||
|
cron: |
||||||
|
name: "certbot renewal ({{ acme_cert_name ~ ' on ' ~ acme_target_server }})" |
||||||
|
job: "ACMEDNS_URL={{ acme_target_server | quote }} \ |
||||||
|
/usr/bin/certbot renew --cert-name {{ acme_cert_name | quote }} --max-log-backups {{ acme_max_log_backups | quote }}" |
||||||
|
hour: "{{ 4 | random(start=1, seed=(host_name ~ acme_cert_name)) }}" |
||||||
|
minute: "{{ 59 | random(seed=(host_name ~ acme_cert_name)) }}" |
@ -0,0 +1,49 @@ |
|||||||
|
#!/bin/sh |
||||||
|
|
||||||
|
{% if (acme_owner is string) and (acme_group is string) and (acme_owner | length > 0) and (acme_group | length > 0) and (acme_use_symlinks | d(true) == true) -%} |
||||||
|
chown -R {{ acme_owner ~ ':' ~ acme_group }} {{ (acme_directory ~ '/archive/' ~ acme_cert_name ~ '/') | quote }} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
|
||||||
|
{{ acme_before_copy_hook | d('') }} |
||||||
|
|
||||||
|
|
||||||
|
{% if (acme_cert is string) and (acme_cert | length > 0) and (acme_use_symlinks | d(true) == false) -%} |
||||||
|
cp -fpT {{ (acme_directory ~ '/live/' ~ acme_cert_name ~ '/fullchain.pem') | quote }} {{ acme_cert | quote }} |
||||||
|
{% if (acme_owner is not string) and (acme_group is string) -%} |
||||||
|
chgrp -f {{ acme_group }} {{ acme_cert | quote }} |
||||||
|
{% elif acme_owner is defined -%} |
||||||
|
chown -f {{ acme_owner ~ ((':' ~ acme_group) if acme_group is string else '') }} {{ acme_cert | quote }} |
||||||
|
{% endif -%} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
{% if (acme_key is string) and (acme_key | length > 0) and (acme_use_symlinks | d(true) == false) -%} |
||||||
|
cp -fpT {{ (acme_directory ~ '/live/' ~ acme_cert_name ~ '/privkey.pem') | quote }} {{ acme_key | quote }} |
||||||
|
{% if (acme_owner is not string) and (acme_group is string) -%} |
||||||
|
chgrp -f {{ acme_group }} {{ acme_key | quote }} |
||||||
|
{% elif acme_owner is defined -%} |
||||||
|
chown -f {{ acme_owner ~ ((':' ~ acme_group) if acme_group is string else '') }} {{ acme_key | quote }} |
||||||
|
{% endif -%} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
{% if (acme_cert_single is string) and (acme_cert_single | length > 0) and (acme_use_symlinks | d(true) == false) -%} |
||||||
|
cp -fpT {{ (acme_directory ~ '/live/' ~ acme_cert_name ~ '/cert.pem') | quote }} {{ acme_cert_single | quote }} |
||||||
|
{% if (acme_owner is not string) and (acme_group is string) -%} |
||||||
|
chgrp -f {{ acme_group }} {{ acme_cert_single | quote }} |
||||||
|
{% elif acme_owner is defined -%} |
||||||
|
chown -f {{ acme_owner ~ ((':' ~ acme_group) if acme_group is string else '') }} {{ acme_cert_single | quote }} |
||||||
|
{% endif -%} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
{% if (acme_chain is string) and (acme_chain | length > 0) and (acme_use_symlinks | d(true) == false) -%} |
||||||
|
cp -fpT {{ (acme_directory ~ '/live/' ~ acme_cert_name ~ '/chain.pem') | quote }} {{ acme_chain | quote }} |
||||||
|
{% if (acme_owner is not string) and (acme_group is string) -%} |
||||||
|
chgrp -f {{ acme_group }} {{ acme_chain | quote }} |
||||||
|
{% elif acme_owner is defined -%} |
||||||
|
chown -f {{ acme_owner ~ ((':' ~ acme_group) if acme_group is string else '') }} {{ acme_chain | quote }} |
||||||
|
{% endif -%} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
|
||||||
|
{{ (acme_post_hook ~ ' &>/dev/null &') if acme_post_hook is defined else '' }} |
||||||
|
|
@ -0,0 +1 @@ |
|||||||
|
ansible_dir: /etc/ansible |
@ -0,0 +1,31 @@ |
|||||||
|
- name: install ansible and dependencies |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- ansible |
||||||
|
- py3-lxml |
||||||
|
- py3-pip |
||||||
|
- py3-requests |
||||||
|
- py3-netaddr |
||||||
|
|
||||||
|
|
||||||
|
- name: install python dependencies |
||||||
|
pip: |
||||||
|
name: |
||||||
|
- pywinrm |
||||||
|
- pywinrm[credssp] |
||||||
|
|
||||||
|
|
||||||
|
- name: create ansible directory |
||||||
|
file: |
||||||
|
path: "{{ ansible_dir }}" |
||||||
|
state: directory |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ ansible_dir }}" |
@ -0,0 +1,620 @@ |
|||||||
|
asterisk_user: asterisk |
||||||
|
asterisk_group: asterisk |
||||||
|
|
||||||
|
asterisk_dir: /var/lib/asterisk |
||||||
|
asterisk_conf_dir: /etc/asterisk |
||||||
|
asterisk_tls_dir: "{{ asterisk_conf_dir }}/tls" |
||||||
|
asterisk_recordings_dir: /opt/recordings |
||||||
|
asterisk_data_dir: "{{ asterisk_dir }}" |
||||||
|
|
||||||
|
asterisk_users: {} |
||||||
|
asterisk_trunks: {} |
||||||
|
|
||||||
|
asterisk_language: ru |
||||||
|
|
||||||
|
asterisk_pjsip_ciphers: |
||||||
|
- ECDHE-ECDSA-CHACHA20-POLY1305 |
||||||
|
- ECDHE-ECDSA-AES256-GCM-SHA384 |
||||||
|
- ECDHE-ECDSA-AES128-GCM-SHA256 |
||||||
|
- ECDHE-RSA-CHACHA20-POLY1305 |
||||||
|
- ECDHE-RSA-AES256-GCM-SHA384 |
||||||
|
- ECDHE-RSA-AES128-GCM-SHA256 |
||||||
|
- DHE-RSA-AES128-SHA256 |
||||||
|
|
||||||
|
|
||||||
|
# meta definitions: |
||||||
|
# __template__ (bool) (section): this section is a template |
||||||
|
# __template_from__ (string/list) (section): templates to inherit from |
||||||
|
# __comment__ (string) (section): specify a comment before the section definition |
||||||
|
# __inner_objects__ (boolean) (config/section): use object syntax when enumerating section members |
||||||
|
|
||||||
|
asterisk_default_config: |
||||||
|
acl: |
||||||
|
acl_lan_clients: |
||||||
|
deny: |
||||||
|
- 0.0.0.0/0.0.0.0 |
||||||
|
permit: |
||||||
|
- "{{ int_net | ansible.utils.ipaddr('network') }}/{{ int_net | ansible.utils.ipaddr('netmask') }}" |
||||||
|
acl_inet_clients: |
||||||
|
deny: |
||||||
|
- "{{ int_net | ansible.utils.ipaddr('network') }}/{{ int_net | ansible.utils.ipaddr('netmask') }}" |
||||||
|
permit: |
||||||
|
- 0.0.0.0/0.0.0.0 |
||||||
|
|
||||||
|
asterisk: |
||||||
|
directories: |
||||||
|
__template__: yes |
||||||
|
__inner_objects__: yes |
||||||
|
astetcdir: "{{ asterisk_conf_dir }}" |
||||||
|
astvarlibdir: "{{ asterisk_dir }}" |
||||||
|
astdatadir: "{{ asterisk_data_dir }}" |
||||||
|
astdbdir: "{{ asterisk_db_dir | d(asterisk_dir) }}" |
||||||
|
astkeydir: "{{ asterisk_key_dir | d(asterisk_dir) }}" |
||||||
|
astagidir: "{{ asterisk_agi_dir | d(asterisk_dir ~ '/agi-bin') }}" |
||||||
|
astspooldir: "{{ asterisk_spool_dir | d('/var/spool/asterisk') }}" |
||||||
|
astrundir: "{{ asterisk_run_dir | d('/var/run/asterisk') }}" |
||||||
|
astlogdir: "{{ asterisk_log_dir | d('/var/log/asterisk') }}" |
||||||
|
astsbindir: /usr/sbin |
||||||
|
astmoddir: /usr/lib/asterisk/modules |
||||||
|
|
||||||
|
options: |
||||||
|
verbose: 0 |
||||||
|
debug: no |
||||||
|
trace: 0 |
||||||
|
|
||||||
|
execincludes: no |
||||||
|
highpriority: yes |
||||||
|
initcrypto: yes |
||||||
|
nocolor: yes |
||||||
|
dumpcore: no |
||||||
|
runuser: "{{ asterisk_user }}" |
||||||
|
rungroup: "{{ asterisk_group }}" |
||||||
|
autosystemname: yes |
||||||
|
maxcalls: 200 |
||||||
|
maxload: "100.0" |
||||||
|
minmemfree: 1 |
||||||
|
languageprefix: yes |
||||||
|
transmit_silence: no |
||||||
|
|
||||||
|
defaultlanguage: en |
||||||
|
documentation_language: en_US |
||||||
|
|
||||||
|
ccss: |
||||||
|
general: |
||||||
|
cc_max_requests: 15 |
||||||
|
|
||||||
|
cdr: |
||||||
|
general: |
||||||
|
enable: yes |
||||||
|
unanswered: yes |
||||||
|
congestion: yes |
||||||
|
|
||||||
|
cel: |
||||||
|
general: |
||||||
|
enable: no |
||||||
|
|
||||||
|
cdr_pgsql: |
||||||
|
global: |
||||||
|
hostname: "{{ database_host }}" |
||||||
|
port: 5432 |
||||||
|
user: "{{ database_user | d('cdr') }}" |
||||||
|
dbname: "{{ database_name | d('cdr') }}" |
||||||
|
table: "{{ database_table | d('cdr') }}" |
||||||
|
password: "{{ database_pass }}" |
||||||
|
encoding: UNICODE |
||||||
|
|
||||||
|
cli_aliases: |
||||||
|
general: |
||||||
|
template: friendly |
||||||
|
friendly: |
||||||
|
"hangup request": channel request hangup |
||||||
|
"originate": channel originate |
||||||
|
"help": core show help |
||||||
|
"pri intense debug span": pri set debug intense span |
||||||
|
"reload": module reload |
||||||
|
"pjsip reload": module reload res_pjsip.so res_pjsip_authenticator_digest.so res_pjsip_endpoint_identifier_ip.so res_pjsip_mwi.so res_pjsip_notify.so res_pjsip_outbound_publish.so res_pjsip_publish_asterisk.so res_pjsip_outbound_registration.so |
||||||
|
|
||||||
|
cli_permissions: |
||||||
|
general: |
||||||
|
default_perm: permit |
||||||
|
|
||||||
|
codecs: |
||||||
|
plc: |
||||||
|
__inner_objects__: yes |
||||||
|
genericplc: "true" |
||||||
|
genericplc_on_equal_codecs: "false" |
||||||
|
opus: |
||||||
|
type: opus |
||||||
|
packet_loss: 2 |
||||||
|
signal: voice |
||||||
|
|
||||||
|
confbridge: |
||||||
|
default_user: |
||||||
|
type: user |
||||||
|
dsp_drop_silence: yes |
||||||
|
jitterbuffer: yes |
||||||
|
default_bridge: |
||||||
|
type: bridge |
||||||
|
max_members: 30 |
||||||
|
language: "{{ asterisk_language }}" |
||||||
|
|
||||||
|
features: |
||||||
|
__inner_objects__: yes |
||||||
|
featuremap: |
||||||
|
blindxfer: "**" |
||||||
|
atxfer: "*#" |
||||||
|
applicationmap: |
||||||
|
volume-up-tx: "#1,self/caller,Gosub(volume-up-tx,s,1)" |
||||||
|
volume-up-rx: "#2,self/caller,Gosub(volume-up-rx,s,1)" |
||||||
|
volume-down-tx: "#3,self/caller,Gosub(volume-down-tx,s,1)" |
||||||
|
volume-down-rx: "#4,self/caller,Gosub(volume-down-rx,s,1)" |
||||||
|
volume-increase-all: "#5,self/caller,Gosub(volume-increase-all,s,1)" |
||||||
|
call-controls: |
||||||
|
volume-up-tx: "" |
||||||
|
volume-up-rx: "" |
||||||
|
volume-down-tx: "" |
||||||
|
volume-down-rx: "" |
||||||
|
volume-increase-all: "" |
||||||
|
|
||||||
|
followme: |
||||||
|
__inner_objects__: yes |
||||||
|
general: |
||||||
|
featuredigittimeout: 3500 |
||||||
|
enable_callee_prompt: "true" |
||||||
|
takecall: 1 |
||||||
|
declinecall: 2 |
||||||
|
call_from_prompt: followme/call-from |
||||||
|
norecording_prompt: followme/no-recording |
||||||
|
options_prompt: followme/options |
||||||
|
pls_hold_prompt: followme/pls-hold-while-try |
||||||
|
status_prompt: followme/status |
||||||
|
sorry_prompt: followme/sorry |
||||||
|
connecting_prompt: "" |
||||||
|
default: |
||||||
|
musicclass: default |
||||||
|
context: default |
||||||
|
enable_callee_prompt: "true" |
||||||
|
takecall: 1 |
||||||
|
declinecall: 2 |
||||||
|
call_from_prompt: followme/call-from |
||||||
|
norecording_prompt: followme/no-recording |
||||||
|
options_prompt: followme/options |
||||||
|
pls_hold_prompt: followme/pls-hold-while-try |
||||||
|
status_prompt: followme/status |
||||||
|
sorry_prompt: followme/sorry |
||||||
|
connecting_prompt: "" |
||||||
|
|
||||||
|
indications: |
||||||
|
general: |
||||||
|
country: ru |
||||||
|
ru: |
||||||
|
description: Russian Federation / ex Soviet Union |
||||||
|
ringcadence: "1000,4000" |
||||||
|
dial: "425" |
||||||
|
busy: "425/350,0/350" |
||||||
|
ring: "425/1000,0/4000" |
||||||
|
congestion: "425/175,0/175" |
||||||
|
callwaiting: "425/200,0/5000" |
||||||
|
record: "1400/400,0/15000" |
||||||
|
info: "950/330,1400/330,1800/330,0/1000" |
||||||
|
dialrecall: "425/400,0/40" |
||||||
|
stutter: "!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425" |
||||||
|
|
||||||
|
logger: |
||||||
|
general: |
||||||
|
queue_log: no |
||||||
|
logfiles: |
||||||
|
__inner_objects__: yes |
||||||
|
console: notice,warning,error,verbose,dtmf |
||||||
|
"syslog.local0": "[plain]notice,warning,error" |
||||||
|
|
||||||
|
manager: |
||||||
|
general: |
||||||
|
enabled: yes |
||||||
|
webenabled: no |
||||||
|
port: 5038 |
||||||
|
bindaddr: 0.0.0.0 |
||||||
|
debug: "off" |
||||||
|
allowmultiplelogin: yes |
||||||
|
displayconnects: yes |
||||||
|
timestampevents: yes |
||||||
|
authtimeout: 10 |
||||||
|
|
||||||
|
musiconhold: |
||||||
|
default: |
||||||
|
mode: files |
||||||
|
directory: moh |
||||||
|
|
||||||
|
pjproject: |
||||||
|
startup: |
||||||
|
cache_pools: yes |
||||||
|
|
||||||
|
|
||||||
|
pjsip: |
||||||
|
system: |
||||||
|
type: system |
||||||
|
threadpool_auto_increment: 3 |
||||||
|
timer_t1: 250 |
||||||
|
timer_b: 16000 |
||||||
|
global: |
||||||
|
type: global |
||||||
|
max_forwards: 40 |
||||||
|
keep_alive_interval: 15 |
||||||
|
user_agent: "{{ org }} Asterisk PBX" |
||||||
|
endpoint_identifier_order: username,ip |
||||||
|
default_from_user: pbx |
||||||
|
default_realm: "{{ host_fqdn }}" |
||||||
|
|
||||||
|
transport-common: |
||||||
|
__template__: yes |
||||||
|
type: transport |
||||||
|
tos: cs3 |
||||||
|
cos: 3 |
||||||
|
allow_reload: no |
||||||
|
local_net: "{{ int_net | ansible.utils.ipaddr('network') }}/{{ int_net | ansible.utils.ipaddr('netmask') }}" |
||||||
|
|
||||||
|
transport-ext: |
||||||
|
__template__: yes |
||||||
|
__template_from__: transport-common |
||||||
|
external_media_address: "{{ asterisk_external_ipv4 | d(hostvars[selected_node]['external_ipv4']) }}" |
||||||
|
external_signaling_address: "{{ asterisk_external_ipv4 | d(hostvars[selected_node]['external_ipv4']) }}" |
||||||
|
|
||||||
|
transport-udp: |
||||||
|
__template__: yes |
||||||
|
__template_from__: transport-common |
||||||
|
protocol: udp |
||||||
|
|
||||||
|
transport-tcp: |
||||||
|
__template__: yes |
||||||
|
__template_from__: transport-common |
||||||
|
protocol: tcp |
||||||
|
|
||||||
|
transport-lan: |
||||||
|
__template_from__: transport-udp |
||||||
|
bind: 0.0.0.0:5060 |
||||||
|
|
||||||
|
transport-lan-tcp: |
||||||
|
__template_from__: transport-tcp |
||||||
|
bind: 0.0.0.0:5060 |
||||||
|
|
||||||
|
transport-lan-tls: |
||||||
|
__template_from__: transport-common |
||||||
|
protocol: tls |
||||||
|
bind: 0.0.0.0:5061 |
||||||
|
cert_file: "{{ asterisk_tls_dir }}/asterisk.crt" |
||||||
|
priv_key_file: "{{ asterisk_tls_dir }}/asterisk.key" |
||||||
|
cipher: "{{ asterisk_pjsip_ciphers | join(',') }}" |
||||||
|
method: tlsv1_2 |
||||||
|
require_client_cert: no |
||||||
|
verify_client: no |
||||||
|
verify_server: no |
||||||
|
|
||||||
|
endpoint-common: |
||||||
|
__template__: yes |
||||||
|
type: endpoint |
||||||
|
allow: "!all,opus,g722,alaw,ulaw,g726,ilbc,gsm" |
||||||
|
allow_overlap: no |
||||||
|
send_connected_line: yes |
||||||
|
trust_connected_line: yes |
||||||
|
direct_media: no |
||||||
|
dtmf_mode: auto_info |
||||||
|
force_rport: yes |
||||||
|
ice_support: no |
||||||
|
identify_by: username |
||||||
|
rewrite_contact: yes |
||||||
|
rtp_symmetric: yes |
||||||
|
send_diversion: yes |
||||||
|
send_history_info: yes |
||||||
|
send_pai: no |
||||||
|
send_rpid: no |
||||||
|
use_ptime: yes |
||||||
|
t38_udptl: no |
||||||
|
tone_zone: ru |
||||||
|
language: ru |
||||||
|
tos_audio: ef |
||||||
|
cos_audio: 5 |
||||||
|
rtp_keepalive: 5 |
||||||
|
rtp_timeout: 360 |
||||||
|
rtp_timeout_hold: 720 |
||||||
|
rtcp_mux: yes |
||||||
|
max_video_streams: 0 |
||||||
|
max_audio_streams: 1 |
||||||
|
bundle: no |
||||||
|
sdp_session: "{{ org }} Asterisk PBX" |
||||||
|
sdp_owner: PBX |
||||||
|
suppress_q850_reason_headers: yes |
||||||
|
|
||||||
|
endpoint-trunk: |
||||||
|
__template__: yes |
||||||
|
__template_from__: endpoint-common |
||||||
|
identify_by: ip,username |
||||||
|
trust_id_inbound: yes |
||||||
|
acl: acl_inet_clients |
||||||
|
contact_acl: acl_inet_clients |
||||||
|
|
||||||
|
endpoint-lan: |
||||||
|
__template__: yes |
||||||
|
__template_from__: endpoint-common |
||||||
|
identify_by: username |
||||||
|
trust_id_inbound: no |
||||||
|
trust_id_outbound: yes |
||||||
|
acl: acl_lan_clients |
||||||
|
contact_acl: acl_lan_clients |
||||||
|
context: outbound |
||||||
|
allow_subscribe: yes |
||||||
|
device_state_busy_at: 1 |
||||||
|
sub_min_expiry: 15 |
||||||
|
media_encryption: sdes |
||||||
|
media_encryption_optimistic: yes |
||||||
|
|
||||||
|
auth-common: |
||||||
|
__template__: yes |
||||||
|
type: auth |
||||||
|
auth_type: userpass |
||||||
|
|
||||||
|
registration-common: |
||||||
|
__template__: yes |
||||||
|
type: registration |
||||||
|
expiration: 1800 |
||||||
|
auth_rejection_permanent: no |
||||||
|
max_retries: 10000 |
||||||
|
retry_interval: 20 |
||||||
|
forbidden_retry_interval: 60 |
||||||
|
fatal_retry_interval: 60 |
||||||
|
|
||||||
|
aor-common: |
||||||
|
__template__: yes |
||||||
|
type: aor |
||||||
|
qualify_frequency: 30 |
||||||
|
max_contacts: 2 # https://asterisk.org/pjsip-mis-configuration-can-cause-loss-sip-registrations |
||||||
|
|
||||||
|
__include__: custom_pjsip.conf |
||||||
|
|
||||||
|
|
||||||
|
pjsip_notify: |
||||||
|
__inner_objects__: yes |
||||||
|
clear-mwi: |
||||||
|
Event: message-summary |
||||||
|
Content-type: application/simple-message-summary |
||||||
|
Content: |
||||||
|
- "Messages-Waiting: no" |
||||||
|
- "Message-Account: sip:asterisk@127.0.0.1" |
||||||
|
- "Voice-Message: 0/0 (0/0)" |
||||||
|
- "" |
||||||
|
polycom-check-cfg: |
||||||
|
Event: check-sync |
||||||
|
yealink-reboot: |
||||||
|
Event: check-sync |
||||||
|
|
||||||
|
queues: |
||||||
|
general: |
||||||
|
persistentmembers: no |
||||||
|
autofill: yes |
||||||
|
monitor-type: MixMonitor |
||||||
|
updatecdr: yes |
||||||
|
log_membername_as_agent: yes |
||||||
|
shared_lastcall: yes |
||||||
|
|
||||||
|
queue-template: |
||||||
|
__template__: yes |
||||||
|
musicclass: default |
||||||
|
strategy: ringall |
||||||
|
servicelevel: 30 |
||||||
|
maxlen: 128 |
||||||
|
timeoutpriority: conf |
||||||
|
timeout: 300 |
||||||
|
wrapuptime: 5 |
||||||
|
announce-frequency: 0 |
||||||
|
periodic-announce-frequency: 0 |
||||||
|
announce-position: no |
||||||
|
autopause: yes |
||||||
|
autopausedelay: 60 |
||||||
|
autopausebusy: yes |
||||||
|
joinempty: unavailable |
||||||
|
leavewhenempty: unavailable |
||||||
|
ringinuse: no |
||||||
|
|
||||||
|
queue-single: |
||||||
|
__template__: yes |
||||||
|
__template_from__: queue-template |
||||||
|
weight: 1 |
||||||
|
autopause: no |
||||||
|
context: inbound-queued-inqueue-busy |
||||||
|
|
||||||
|
queue-le: |
||||||
|
__template__: yes |
||||||
|
__template_from__: queue-template |
||||||
|
weight: 1 |
||||||
|
autopause: no |
||||||
|
|
||||||
|
__include__: custom_queues.conf |
||||||
|
|
||||||
|
queuerules: |
||||||
|
general: |
||||||
|
|
||||||
|
rtp: |
||||||
|
general: |
||||||
|
rtpstart: 15000 |
||||||
|
rtpend: 19000 |
||||||
|
strictrtp: yes |
||||||
|
icesupport: "false" |
||||||
|
|
||||||
|
udptl: |
||||||
|
general: |
||||||
|
|
||||||
|
modules: |
||||||
|
modules: |
||||||
|
autoload: no |
||||||
|
load: |
||||||
|
- app_attended_transfer.so |
||||||
|
- app_blind_transfer.so |
||||||
|
- app_bridgeaddchan.so |
||||||
|
- app_bridgewait.so |
||||||
|
- app_cdr.so |
||||||
|
- app_celgenuserevent.so |
||||||
|
- app_chanisavail.so |
||||||
|
- app_channelredirect.so |
||||||
|
- app_chanspy.so |
||||||
|
- app_confbridge.so |
||||||
|
- app_controlplayback.so |
||||||
|
- app_dial.so |
||||||
|
- app_directed_pickup.so |
||||||
|
- app_dumpchan.so |
||||||
|
- app_echo.so |
||||||
|
- app_exec.so |
||||||
|
- app_followme.so |
||||||
|
- app_forkcdr.so |
||||||
|
- app_mixmonitor.so |
||||||
|
- app_originate.so |
||||||
|
- app_playback.so |
||||||
|
- app_queue.so |
||||||
|
- app_read.so |
||||||
|
- app_readexten.so |
||||||
|
- app_senddtmf.so |
||||||
|
- app_softhangup.so |
||||||
|
- app_stack.so |
||||||
|
- app_stream_echo.so |
||||||
|
- app_talkdetect.so |
||||||
|
- app_transfer.so |
||||||
|
- app_verbose.so |
||||||
|
- app_waitforring.so |
||||||
|
- app_waitforsilence.so |
||||||
|
- app_waituntil.so |
||||||
|
- app_while.so |
||||||
|
|
||||||
|
- bridge_builtin_features.so |
||||||
|
- bridge_builtin_interval_features.so |
||||||
|
- bridge_holding.so |
||||||
|
- bridge_native_rtp.so |
||||||
|
- bridge_simple.so |
||||||
|
- bridge_softmix.so |
||||||
|
|
||||||
|
- cdr_pgsql.so |
||||||
|
|
||||||
|
- chan_bridge_media.so |
||||||
|
- chan_pjsip.so |
||||||
|
- chan_rtp.so |
||||||
|
|
||||||
|
- codec_a_mu.so |
||||||
|
- codec_adpcm.so |
||||||
|
- codec_alaw.so |
||||||
|
- codec_g722.so |
||||||
|
- codec_g726.so |
||||||
|
- codec_gsm.so |
||||||
|
- codec_ilbc.so |
||||||
|
- codec_opus_open_source.so |
||||||
|
- codec_resample.so |
||||||
|
- codec_ulaw.so |
||||||
|
|
||||||
|
- format_g719.so |
||||||
|
- format_g723.so |
||||||
|
- format_g726.so |
||||||
|
- format_gsm.so |
||||||
|
- format_ilbc.so |
||||||
|
- format_pcm.so |
||||||
|
- format_sln.so |
||||||
|
- format_vox.so |
||||||
|
- format_wav.so |
||||||
|
- format_wav_gsm.so |
||||||
|
|
||||||
|
- func_blacklist.so |
||||||
|
- func_callcompletion.so |
||||||
|
- func_callerid.so |
||||||
|
- func_cdr.so |
||||||
|
- func_channel.so |
||||||
|
- func_config.so |
||||||
|
- func_cut.so |
||||||
|
- func_devstate.so |
||||||
|
- func_dialplan.so |
||||||
|
- func_global.so |
||||||
|
- func_hangupcause.so |
||||||
|
- func_holdintercept.so |
||||||
|
- func_jitterbuffer.so |
||||||
|
- func_logic.so |
||||||
|
- func_module.so |
||||||
|
- func_pjsip_aor.so |
||||||
|
- func_pjsip_contact.so |
||||||
|
- func_pjsip_endpoint.so |
||||||
|
- func_rand.so |
||||||
|
- func_sorcery.so |
||||||
|
- func_strings.so |
||||||
|
- func_talkdetect.so |
||||||
|
- func_timeout.so |
||||||
|
- func_volume.so |
||||||
|
|
||||||
|
- pbx_config.so |
||||||
|
- pbx_loopback.so |
||||||
|
- pbx_realtime.so |
||||||
|
- pbx_spool.so |
||||||
|
|
||||||
|
- res_audiosocket.so |
||||||
|
- res_clialiases.so |
||||||
|
- res_clioriginate.so |
||||||
|
- res_convert.so |
||||||
|
- res_crypto.so |
||||||
|
- res_format_attr_celt.so |
||||||
|
- res_format_attr_g729.so |
||||||
|
- res_format_attr_ilbc.so |
||||||
|
- res_format_attr_opus.so |
||||||
|
- res_format_attr_silk.so |
||||||
|
- res_format_attr_siren14.so |
||||||
|
- res_format_attr_siren7.so |
||||||
|
- res_musiconhold.so |
||||||
|
- res_mutestream.so |
||||||
|
- res_pjproject.so |
||||||
|
|
||||||
|
- res_pjsip.so |
||||||
|
- res_pjsip_acl.so |
||||||
|
- res_pjsip_authenticator_digest.so |
||||||
|
- res_pjsip_caller_id.so |
||||||
|
- res_pjsip_dialog_info_body_generator.so |
||||||
|
- res_pjsip_diversion.so |
||||||
|
- res_pjsip_dlg_options.so |
||||||
|
- res_pjsip_dtmf_info.so |
||||||
|
- res_pjsip_empty_info.so |
||||||
|
- res_pjsip_endpoint_identifier_ip.so |
||||||
|
- res_pjsip_endpoint_identifier_user.so |
||||||
|
- res_pjsip_exten_state.so |
||||||
|
- res_pjsip_header_funcs.so |
||||||
|
- res_pjsip_history.so |
||||||
|
- res_pjsip_logger.so |
||||||
|
- res_pjsip_messaging.so |
||||||
|
- res_pjsip_mwi.so |
||||||
|
- res_pjsip_mwi_body_generator.so |
||||||
|
- res_pjsip_nat.so |
||||||
|
- res_pjsip_notify.so |
||||||
|
- res_pjsip_outbound_authenticator_digest.so |
||||||
|
- res_pjsip_outbound_publish.so |
||||||
|
- res_pjsip_outbound_registration.so |
||||||
|
- res_pjsip_path.so |
||||||
|
- res_pjsip_pidf_body_generator.so |
||||||
|
- res_pjsip_publish_asterisk.so |
||||||
|
- res_pjsip_pubsub.so |
||||||
|
- res_pjsip_refer.so |
||||||
|
- res_pjsip_registrar.so |
||||||
|
- res_pjsip_rfc3326.so |
||||||
|
- res_pjsip_sdp_rtp.so |
||||||
|
- res_pjsip_send_to_voicemail.so |
||||||
|
- res_pjsip_session.so |
||||||
|
- res_pjsip_sips_contact.so |
||||||
|
- res_pjsip_xpidf_body_generator.so |
||||||
|
|
||||||
|
- res_rtp_asterisk.so |
||||||
|
- res_rtp_multicast.so |
||||||
|
- res_security_log.so |
||||||
|
- res_sorcery_astdb.so |
||||||
|
- res_sorcery_config.so |
||||||
|
- res_sorcery_memory.so |
||||||
|
- res_sorcery_memory_cache.so |
||||||
|
- res_srtp.so |
||||||
|
- res_stasis.so |
||||||
|
- res_stasis_answer.so |
||||||
|
- res_stasis_device_state.so |
||||||
|
- res_stasis_playback.so |
||||||
|
- res_stasis_recording.so |
||||||
|
- res_timing_pthread.so |
||||||
|
- res_timing_timerfd.so |
||||||
|
|
||||||
|
- res_pjsip_header_funcs.so |
||||||
|
- res_pjsip_history.so |
||||||
|
- res_pjsip_sdp_rtp.so |
@ -0,0 +1,8 @@ |
|||||||
|
- name: handle config change |
||||||
|
import_tasks: asterisk_handlers.yml |
||||||
|
|
||||||
|
|
||||||
|
- name: restart asterisk |
||||||
|
service: |
||||||
|
name: asterisk |
||||||
|
state: restarted |
@ -0,0 +1,19 @@ |
|||||||
|
- block: |
||||||
|
- name: restart asterisk |
||||||
|
service: |
||||||
|
name: asterisk |
||||||
|
state: restarted |
||||||
|
when: item.item.action is not defined |
||||||
|
|
||||||
|
|
||||||
|
- name: reload dialplan |
||||||
|
command: |
||||||
|
cmd: 'asterisk -rx "dialplan reload"' |
||||||
|
when: item.item.action == 'reload dialplan' |
||||||
|
|
||||||
|
|
||||||
|
- name: reload configs |
||||||
|
command: |
||||||
|
cmd: 'asterisk -rx "core reload"' |
||||||
|
when: item.item.action == 'reload configs' |
||||||
|
when: item is defined |
@ -0,0 +1,194 @@ |
|||||||
|
- name: set asterisk_cfg |
||||||
|
set_fact: |
||||||
|
asterisk_cfg: "{{ asterisk_default_config | d({}) | combine(asterisk_config | d({}), recursive=true) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: install dependencies |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- asterisk |
||||||
|
- asterisk-pgsql |
||||||
|
- asterisk-openrc |
||||||
|
- asterisk-opus |
||||||
|
- asterisk-srtp |
||||||
|
- tar |
||||||
|
- vorbis-tools |
||||||
|
|
||||||
|
|
||||||
|
- name: create user and group |
||||||
|
include_tasks: tasks/create_user.yml |
||||||
|
vars: |
||||||
|
user: |
||||||
|
name: "{{ asterisk_user }}" |
||||||
|
group: "{{ asterisk_group }}" |
||||||
|
dir: "{{ asterisk_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure asterisk directories exist |
||||||
|
file: |
||||||
|
path: "{{ item }}" |
||||||
|
state: directory |
||||||
|
owner: "{{ asterisk_user }}" |
||||||
|
group: "{{ asterisk_group }}" |
||||||
|
loop: |
||||||
|
- "{{ asterisk_dir }}" |
||||||
|
- "{{ asterisk_conf_dir }}" |
||||||
|
- "{{ asterisk_tls_dir }}" |
||||||
|
- "{{ asterisk_data_dir }}" |
||||||
|
- "{{ asterisk_data_dir }}/moh" |
||||||
|
- "{{ asterisk_data_dir }}/sounds" |
||||||
|
- "{{ asterisk_data_dir }}/sounds/{{ asterisk_language }}" |
||||||
|
- "{{ asterisk_data_dir }}/sounds/{{ asterisk_language }}/custom" |
||||||
|
- "{{ asterisk_recordings_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: template custom asterisk configs |
||||||
|
template: |
||||||
|
src: "{{ item }}.j2" |
||||||
|
dest: "{{ asterisk_conf_dir }}/{{ item }}.conf" |
||||||
|
force: yes |
||||||
|
mode: 0400 |
||||||
|
owner: "{{ asterisk_user }}" |
||||||
|
group: "{{ asterisk_group }}" |
||||||
|
lstrip_blocks: yes |
||||||
|
notify: restart asterisk |
||||||
|
loop: |
||||||
|
- custom_pjsip |
||||||
|
- custom_queues |
||||||
|
- ext_ivr |
||||||
|
- ext_utils |
||||||
|
- extensions |
||||||
|
|
||||||
|
|
||||||
|
- name: template asterisk configs |
||||||
|
template: |
||||||
|
src: "{{ 'config' if item is string else (item.config | d('config')) }}.j2" |
||||||
|
dest: "{{ asterisk_conf_dir }}/{{ item if item is string else (item.dest | d(item.config) | d(item.name)) }}.conf" |
||||||
|
force: yes |
||||||
|
mode: 0400 |
||||||
|
owner: "{{ asterisk_user }}" |
||||||
|
group: "{{ asterisk_group }}" |
||||||
|
lstrip_blocks: yes |
||||||
|
notify: restart asterisk |
||||||
|
loop: |
||||||
|
- acl |
||||||
|
- asterisk |
||||||
|
- ccss |
||||||
|
- cdr |
||||||
|
- cdr_pgsql |
||||||
|
- cli_aliases |
||||||
|
- cli_permissions |
||||||
|
- codecs |
||||||
|
- confbridge |
||||||
|
- features |
||||||
|
- followme |
||||||
|
- indications |
||||||
|
- logger |
||||||
|
- manager |
||||||
|
- musiconhold |
||||||
|
- pjproject |
||||||
|
- pjsip |
||||||
|
- pjsip_notify |
||||||
|
- queues |
||||||
|
- rtp |
||||||
|
- modules |
||||||
|
- queuerules |
||||||
|
- cel |
||||||
|
- udptl |
||||||
|
|
||||||
|
|
||||||
|
- name: edit service config |
||||||
|
lineinfile: |
||||||
|
path: /etc/conf.d/asterisk |
||||||
|
regexp: "^{{ item.name | upper }}=" |
||||||
|
line: "{{ item.name | upper }}=\"{{ item.value }}\"" |
||||||
|
when: item.when | d(true) |
||||||
|
notify: restart asterisk |
||||||
|
loop: |
||||||
|
- name: asterisk_opts |
||||||
|
value: "-C {{ (asterisk_conf_dir ~ '/asterisk.conf') | quote }}" |
||||||
|
when: "{{ asterisk_conf_dir != '/etc/asterisk' }}" |
||||||
|
- name: asterisk_user |
||||||
|
value: "{{ asterisk_user }}" |
||||||
|
- name: asterisk_nice |
||||||
|
value: "{{ asterisk_niceness | d(None) }}" |
||||||
|
when: "{{ asterisk_niceness is defined }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: download asterisk sound pack |
||||||
|
get_url: |
||||||
|
url: "https://downloads.asterisk.org/pub/telephony/sounds/asterisk-core-sounds-{{ asterisk_language }}-{{ item }}-current.tar.gz" |
||||||
|
dest: "{{ asterisk_data_dir }}/{{ asterisk_language }}-{{ item }}.tar.gz" |
||||||
|
owner: "{{ asterisk_user }}" |
||||||
|
group: "{{ asterisk_group }}" |
||||||
|
register: result |
||||||
|
loop: |
||||||
|
- sln16 |
||||||
|
- wav |
||||||
|
|
||||||
|
|
||||||
|
- name: extract sound pack |
||||||
|
unarchive: |
||||||
|
src: "{{ item }}" |
||||||
|
dest: "{{ asterisk_data_dir }}/sounds/{{ asterisk_language }}" |
||||||
|
remote_src: yes |
||||||
|
owner: "{{ asterisk_user }}" |
||||||
|
group: "{{ asterisk_group }}" |
||||||
|
loop: "{{ result.results | d([]) | selectattr('dest', 'defined') | selectattr('changed', 'defined') | selectattr('changed', 'equalto', true) | map(attribute='dest') | list }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: deploy RSA cert for SIP TLS |
||||||
|
include_role: |
||||||
|
name: certs |
||||||
|
vars: |
||||||
|
certs: |
||||||
|
id: ast-tls |
||||||
|
cert: "{{ asterisk_tls_dir }}/asterisk.crt" |
||||||
|
key: "{{ asterisk_tls_dir }}/asterisk.key" |
||||||
|
chain: "{{ asterisk_tls_dir }}/chain.crt" |
||||||
|
owner: "{{ asterisk_user }}" |
||||||
|
group: "{{ asterisk_group }}" |
||||||
|
post_hook: service asterisk restart |
||||||
|
notify: restart asterisk |
||||||
|
|
||||||
|
|
||||||
|
- name: install and configure cdr |
||||||
|
include_role: |
||||||
|
name: cdr |
||||||
|
vars: |
||||||
|
cdr_group: "{{ asterisk_group }}" |
||||||
|
cdr_config: |
||||||
|
db_host: "{{ asterisk_cfg.cdr_pgsql.global.hostname }}" |
||||||
|
db_user: "{{ asterisk_cfg.cdr_pgsql.global.user }}" |
||||||
|
db_pass: "{{ asterisk_cfg.cdr_pgsql.global.password }}" |
||||||
|
db_database: "{{ asterisk_cfg.cdr_pgsql.global.dbname }}" |
||||||
|
db_table: "{{ asterisk_cfg.cdr_pgsql.global.table }}" |
||||||
|
record_dir: "{{ asterisk_recordings_dir }}" |
||||||
|
ami_user: "{{ asterisk_ami_cdr_user }}" |
||||||
|
ami_pass: "{{ asterisk_ami_cdr_secret }}" |
||||||
|
when: asterisk_use_cdr | d(true) == true |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ asterisk_conf_dir }}" |
||||||
|
- "{{ asterisk_tls_dir }}" |
||||||
|
- "{{ asterisk_data_dir }}/moh" |
||||||
|
- "{{ asterisk_data_dir }}/sounds/{{ asterisk_language }}/custom" |
||||||
|
- "{{ asterisk_dir }}/astdb.sqlite3" |
||||||
|
|
||||||
|
|
||||||
|
- name: enable and start asterisk |
||||||
|
service: |
||||||
|
name: asterisk |
||||||
|
enabled: yes |
||||||
|
state: started |
@ -0,0 +1,85 @@ |
|||||||
|
{% macro config_template(config_name, asterisk_cfg) -%} |
||||||
|
{% set ns = namespace(objects=false) -%} |
||||||
|
|
||||||
|
{% if config_name is string and asterisk_cfg[config_name] is mapping -%} |
||||||
|
{% for section in (asterisk_cfg[config_name] | dict2items) -%} |
||||||
|
{% if section.value is mapping -%} |
||||||
|
{% set template_parts = [] -%} |
||||||
|
|
||||||
|
{% if (section.value['__template__'] is boolean) and (section.value['__template__'] == true) -%} |
||||||
|
{% set template_parts = template_parts + ['!'] -%} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
{% if section.value['__template_from__'] is string -%} |
||||||
|
{% set template_parts = template_parts + [section.value['__template_from__']] -%} |
||||||
|
{% elif section.value['__template_from__'] | type_debug == 'list' -%} |
||||||
|
{% set template_parts = template_parts + section.value['__template_from__'] -%} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
{% if section.value['__comment__'] is string -%} |
||||||
|
; {{ section.value['__comment__'] }} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
|
||||||
|
{% if template_parts | length == 0 -%} |
||||||
|
[{{ section.key }}] |
||||||
|
{% else -%} |
||||||
|
[{{ section.key }}]({{ template_parts | join(',') }}) |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
{% set ns.objects = (section.value['__inner_objects__'] | d(asterisk_cfg[config_name]['__inner_objects__'] | d(false))) -%} |
||||||
|
|
||||||
|
{% for option in (section.value | dict2items) -%} |
||||||
|
{% if not option.key.startswith('__') and not option.key.endswith('__') -%} |
||||||
|
|
||||||
|
{% if option.value | type_debug == 'list' -%} |
||||||
|
{% if option.value | length == 0 -%} |
||||||
|
{{ option.key }} => |
||||||
|
{% else -%} |
||||||
|
{% for option_element in option.value -%} |
||||||
|
{% set option_value = 'yes' if (option_element is boolean and option_element == true) else ('no' if (option_element is boolean and option_element == false) else option_element ) -%} |
||||||
|
{{ option.key }} => {{ option_value }} |
||||||
|
{% endfor -%} |
||||||
|
{% endif -%} |
||||||
|
{% elif option.value is mapping -%} |
||||||
|
{% set option_is_object = option.value['__inner_objects__'] | d(ns.objects) -%} |
||||||
|
|
||||||
|
{% if option.value['__comment__'] is string -%} |
||||||
|
; {{ option.value['__comment__'] }} |
||||||
|
{% endif -%} |
||||||
|
{% if option.value['__include_before__'] is string -%} |
||||||
|
#include {{ option.value['__include_before__'] }} |
||||||
|
{% endif -%} |
||||||
|
{% if option.value['__try_include_before__'] is string -%} |
||||||
|
#tryinclude {{ option.value['__try_include_before__'] }} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
{% set option_value = 'yes' if (option.value['__value__'] is boolean and option.value['__value__'] == true) else ('no' if (option.value['__value__'] is boolean and option.value['__value__'] == false) else option.value['__value__'] ) -%} |
||||||
|
{{ option.key }} {{ '=>' if option_is_object else '=' }} {{ option_value }} |
||||||
|
|
||||||
|
{% if option.value['__include_after__'] is string -%} |
||||||
|
#include {{ option.value['__include_after__'] }} |
||||||
|
{% endif -%} |
||||||
|
{% if option.value['__try_include_after__'] is string -%} |
||||||
|
#tryinclude {{ option.value['__try_include_after__'] }} |
||||||
|
{% endif -%} |
||||||
|
{% else -%} |
||||||
|
{% set option_value = 'yes' if (option.value is boolean and option.value == true) else ('no' if (option.value is boolean and option.value == false) else option.value ) -%} |
||||||
|
{{ option.key }} {{ '=>' if ns.objects else '=' }} {{ option_value }} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
{% endif -%} |
||||||
|
{% endfor -%} |
||||||
|
{% if not loop.last %} |
||||||
|
|
||||||
|
{% endif -%} |
||||||
|
{% elif (section.key == '__include__') and (section.value is string) -%} |
||||||
|
#include {{ section.value }} |
||||||
|
{% elif (section.key == '__try_include__') and (section.value is string) -%} |
||||||
|
#tryinclude {{ section.value }} |
||||||
|
{% endif -%} |
||||||
|
{% endfor -%} |
||||||
|
{% endif -%} |
||||||
|
{% endmacro -%} |
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@ |
|||||||
|
{%- from '_macros.j2' import config_template -%} |
||||||
|
|
||||||
|
{{- config_template(item if (item is string) else (item.config | d(item.name)), asterisk_cfg) -}} |
@ -0,0 +1,75 @@ |
|||||||
|
{% macro trunk_options(opts) -%} |
||||||
|
{% for opt in (opts | d({}) | dict2items) -%} |
||||||
|
{{ opt.key }} = {{ 'yes' if (opt.value is boolean and opt.value == true) else ('no' if (opt.value is boolean and opt.value == false) else opt.value ) }} |
||||||
|
{% endfor -%} |
||||||
|
{% endmacro -%} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% for user in asterisk_users | d({}) | dict2items -%} |
||||||
|
{% if user.value is mapping -%} |
||||||
|
{% if user.value['__comment__'] is string -%} |
||||||
|
; {{ user.value['__comment__'] }} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
[auth-{{ user.key }}](auth-common) |
||||||
|
username = {{ user.value['login'] | d(user.key) }} |
||||||
|
password = {{ user.value['password'] }} |
||||||
|
|
||||||
|
[{{ user.key }}](aor-common) |
||||||
|
|
||||||
|
[{{ user.key }}](endpoint-lan) |
||||||
|
auth = auth-{{ user.key }} |
||||||
|
aors = {{ user.key }} |
||||||
|
callerid = {{ user.value['callerid'] | d(user.key) }} <{{ user.key }}> |
||||||
|
|
||||||
|
|
||||||
|
{% endif -%} |
||||||
|
{% endfor %} |
||||||
|
|
||||||
|
{% for trunk in asterisk_trunks | d({}) | dict2items -%} |
||||||
|
{% if trunk.value is mapping -%} |
||||||
|
{% if trunk.value['__comment__'] is string -%} |
||||||
|
; {{ trunk.value['__comment__'] }} |
||||||
|
{% endif -%} |
||||||
|
|
||||||
|
[transport-{{ trunk.key }}](transport-udp,transport-ext) |
||||||
|
{{ trunk_options(trunk.value['transport']) }} |
||||||
|
{# #} |
||||||
|
[registration-{{ trunk.key }}](registration-common) |
||||||
|
outbound_auth = auth-{{ trunk.key }} |
||||||
|
endpoint = endpoint-{{ trunk.key }} |
||||||
|
transport = transport-{{ trunk.key }} |
||||||
|
{{ trunk_options(trunk.value['registration']) }} |
||||||
|
{# #} |
||||||
|
[auth-{{ trunk.key }}](auth-common) |
||||||
|
{{ trunk_options(trunk.value['auth']) }} |
||||||
|
{# #} |
||||||
|
[aor-{{ trunk.key }}](aor-common) |
||||||
|
{{ trunk_options(trunk.value['aor']) }} |
||||||
|
{# #} |
||||||
|
[endpoint-{{ trunk.key }}](endpoint-trunk) |
||||||
|
transport = transport-{{ trunk.key }} |
||||||
|
context = inbound-{{ trunk.key }} |
||||||
|
outbound_auth = auth-{{ trunk.key }} |
||||||
|
aors = aor-{{ trunk.key }} |
||||||
|
{{ trunk_options(trunk.value['endpoint']) }} |
||||||
|
{# #} |
||||||
|
[identify-{{ trunk.key }}] |
||||||
|
type = identify |
||||||
|
endpoint = endpoint-{{ trunk.key }} |
||||||
|
{{ trunk_options(trunk.value['identify']) }} |
||||||
|
|
||||||
|
{%- if not loop.last %} |
||||||
|
|
||||||
|
|
||||||
|
{% endif -%} |
||||||
|
{% endif -%} |
||||||
|
{% endfor %} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[reslist-all] |
||||||
|
type=resource_list |
||||||
|
event=presence |
||||||
|
list_item={{ asterisk_users | d({}) | dict2items | map(attribute='key') | list | join(',') }} |
@ -0,0 +1,26 @@ |
|||||||
|
{% for user in asterisk_users | d({}) | dict2items -%} |
||||||
|
{% if user.value is mapping -%} |
||||||
|
[queue-{{ user.key }}]({{ user.value['self_queue_type'] | d('queue-single') }}) |
||||||
|
member => PJSIP/{{ user.key }},0,{{ user.value['callerid'] | d(user.key) }} |
||||||
|
{% endif -%} |
||||||
|
{% endfor %} |
||||||
|
|
||||||
|
|
||||||
|
{% set defined_queues = (asterisk_users | d({}) | dict2items | map(attribute='value') | list | selectattr('queues', 'defined') | map(attribute='queues') | list | flatten | unique | list) -%} |
||||||
|
{% set auto_queues = (asterisk_users | d({}) | dict2items | rejectattr('value.queues', 'defined') | map(attribute='key') | list) -%} |
||||||
|
{% set all_queues = ((defined_queues | d([])) + (auto_queues | d([])) | unique | list) -%} |
||||||
|
|
||||||
|
{% for queue in defined_queues -%} |
||||||
|
{% if asterisk_users[queue] is not defined -%} |
||||||
|
{% set queue_users = (asterisk_users | d({}) | dict2items | selectattr('value.queues', 'defined') | selectattr('value.queues', 'contains', queue) | list) -%} |
||||||
|
{% if queue_users | length > 1 -%} |
||||||
|
[queue-{{ queue }}](queue-template) |
||||||
|
{% for user in queue_users -%} |
||||||
|
member => PJSIP/{{ user.key }},0,{{ user.value['callerid'] | d(user.key) }} |
||||||
|
{% endfor -%} |
||||||
|
{%- if not loop.last %} |
||||||
|
|
||||||
|
{% endif -%} |
||||||
|
{% endif -%} |
||||||
|
{% endif -%} |
||||||
|
{% endfor -%} |
@ -0,0 +1,58 @@ |
|||||||
|
; IVR |
||||||
|
; 1 - went to IVR |
||||||
|
; 2 - pressed a button |
||||||
|
; 3 - did not press anything |
||||||
|
|
||||||
|
|
||||||
|
[ivr-dial] |
||||||
|
exten => s,1,Set(CDR(ivr)=2) |
||||||
|
same => n,Gosub(inbound-queued,s,1(${ARG1})) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
[ivr-dial-all] |
||||||
|
exten => s,1,Set(CDR(ivr)=3) |
||||||
|
same => n,Queue(queue-all,inrt,,,,,,pre-call) |
||||||
|
|
||||||
|
|
||||||
|
[ivr-select] |
||||||
|
exten => 1,1,Gosub(ivr-dial,s,1(1)) |
||||||
|
exten => 2,1,Gosub(ivr-dial,s,1(3)) |
||||||
|
exten => 3,1,Gosub(ivr-dial,s,1(2)) |
||||||
|
exten => 4,1,Gosub(ivr-dial,s,1(11)) |
||||||
|
exten => 5,1,Gosub(ivr-dial,s,1(9)) |
||||||
|
|
||||||
|
|
||||||
|
[ivr] |
||||||
|
exten => s,1,Answer(250) |
||||||
|
same => n,Set(CDR(ivr)=1) |
||||||
|
same => n,Set(TIMEOUT(digit)=3) |
||||||
|
same => n,Set(TIMEOUT(response)=3) |
||||||
|
same => n,Background(custom/ivr-intro-12-2021,m,,ivr-select) |
||||||
|
same => n,WaitExten(3) |
||||||
|
same => n,Gosub(ivr-dial-all,s,1) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[ivr-select-spb] |
||||||
|
exten => 1,1,Gosub(ivr-dial,s,1(6)) |
||||||
|
exten => 2,1,Gosub(ivr-dial,s,1(8)) |
||||||
|
|
||||||
|
[ivr-dial-all-spb] |
||||||
|
exten => s,1,Set(CDR(ivr)=3) |
||||||
|
same => n,Queue(queue-spb,inrt,,,,,,pre-call) |
||||||
|
|
||||||
|
[ivr-spb] |
||||||
|
exten => s,1,Answer(250) |
||||||
|
same => n,Set(CDR(ivr)=1) |
||||||
|
same => n,Set(TIMEOUT(digit)=3) |
||||||
|
same => n,Set(TIMEOUT(response)=3) |
||||||
|
same => n,Background(custom/ivr-intro-spb,m,,ivr-select-spb) |
||||||
|
same => n,WaitExten(3) |
||||||
|
same => n,Gosub(ivr-dial-all-spb,s,1) |
||||||
|
same => n,Hangup() |
@ -0,0 +1,88 @@ |
|||||||
|
; Extension utilities |
||||||
|
|
||||||
|
|
||||||
|
[record-start] |
||||||
|
exten => s,1,ExecIf($["${IS_RECORDING}"="1"]?Return()) |
||||||
|
same => n,Set(UID=${UNIQUEID}.${RAND(1,100000)}) |
||||||
|
same => n,Set(CDR(actualuniqueid)=${UID}) |
||||||
|
same => n,MixMonitor({{ asterisk_recordings_dir }}/${UID}.wav,b,oggenc -q 5 -o {{ asterisk_recordings_dir }}/${UID}.ogg {{ asterisk_recordings_dir }}/${UID}.wav && rm {{ asterisk_recordings_dir }}/${UID}.wav) |
||||||
|
same => n,Set(__IS_RECORDING=1) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
[record-stop] |
||||||
|
exten => s,1,StopMixMonitor() |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
|
||||||
|
; Filtering CallerID |
||||||
|
[clear-callerid] |
||||||
|
exten => s,1,Verbose(Filtering CallerID) |
||||||
|
same => n,Set(CALLERID(num)=${FILTER(0-9,${CALLERID(num)})}) |
||||||
|
same => n,Set(CALLERID(name)=) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
|
||||||
|
; Setting up volume control |
||||||
|
[volume-setup] |
||||||
|
exten => s,1,Set(CURRENT_VOLUME_TX=1) |
||||||
|
same => n,Set(CURRENT_VOLUME_RX=1) |
||||||
|
same => n,Set(__DYNAMIC_FEATURES=call-controls) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
[volume-up-tx] |
||||||
|
exten => s,1,Set(CURRENT_VOLUME_TX=$[${CURRENT_VOLUME_TX}*1.25]) |
||||||
|
same => n,Set(VOLUME(TX)=${CURRENT_VOLUME_TX}) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
[volume-up-rx] |
||||||
|
exten => s,1,Set(CURRENT_VOLUME_RX=$[${CURRENT_VOLUME_RX}*1.25]) |
||||||
|
same => n,Set(VOLUME(RX)=${CURRENT_VOLUME_RX}) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
[volume-down-tx] |
||||||
|
exten => s,1,Set(CURRENT_VOLUME_TX=$[${CURRENT_VOLUME_TX}*0.75]) |
||||||
|
same => n,Set(VOLUME(TX)=${CURRENT_VOLUME_TX}) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
[volume-down-rx] |
||||||
|
exten => s,1,Set(CURRENT_VOLUME_RX=$[${CURRENT_VOLUME_RX}*0.75]) |
||||||
|
same => n,Set(VOLUME(RX)=${CURRENT_VOLUME_RX}) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
[volume-increase-all] |
||||||
|
exten => s,1,Set(CURRENT_VOLUME_RX=2) |
||||||
|
same => n,Set(CURRENT_VOLUME_TX=2) |
||||||
|
same => n,Set(VOLUME(RX)=2) |
||||||
|
same => n,Set(VOLUME(TX)=2) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
|
||||||
|
; An invalid extension has been dialed |
||||||
|
[invalid-ext] |
||||||
|
exten => s,1,Answer(250) |
||||||
|
same => n,Playback(custom/invalid-ext) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
; An extension has been dialed, but it is currently offline |
||||||
|
[offline-ext] |
||||||
|
exten => s,1,Answer(250) |
||||||
|
same => n,Playback(custom/this-offline) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; Output "Busy" signal |
||||||
|
[busy] |
||||||
|
exten => s,1,Busy(10) |
||||||
|
same => n,Wait(1) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
; Output "Congestion" signal |
||||||
|
[congestion] |
||||||
|
exten => s,1,Congestion(10) |
||||||
|
same => n,Wait(1) |
||||||
|
same => n,Hangup() |
@ -0,0 +1,303 @@ |
|||||||
|
[general] |
||||||
|
static=yes ; never rewrite this file |
||||||
|
writeprotect=yes |
||||||
|
autofallthrough=yes ; hang up if end of dialplan is reached |
||||||
|
clearglobalvars=yes ; clear global vars on dialplan reload |
||||||
|
|
||||||
|
|
||||||
|
[globals] |
||||||
|
#include ext_utils.conf ; include utilities |
||||||
|
#include ext_ivr.conf ; include IVR |
||||||
|
|
||||||
|
TRANSFER_CONTEXT=transfer |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[transfer] |
||||||
|
exten => 0,1,Verbose(TRANSFER IVR) |
||||||
|
same => n,Set(__IS_RECORDING=0) |
||||||
|
same => n,StopMixMonitor() |
||||||
|
same => n,ForkCDR(erv) |
||||||
|
same => n,Gosub(pre-any,s,1(IVR,TRANSFER)) |
||||||
|
same => n,Gosub(ivr,s,1) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
exten => _Z,1,Verbose(TRANSFER) |
||||||
|
same => n,Set(__IS_RECORDING=0) |
||||||
|
same => n,StopMixMonitor() |
||||||
|
same => n,ForkCDR(erv) |
||||||
|
same => n,Gosub(pre-any,s,1(${EXTEN},TRANSFER)) |
||||||
|
same => n,Gosub(inbound-queued,s,1(${EXTEN})) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
include => catchall |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[pre-any] |
||||||
|
exten => s,1,Gosub(clear-callerid,s,1) |
||||||
|
same => n,Set(__CALLER=${CALLERID(num)}) |
||||||
|
same => n,Set(__CALLEE=${ARG1}) |
||||||
|
same => n,Set(__CALL_OPERATION=${ARG2}) |
||||||
|
same => n,Set(CDR(actualsrc)=${CALLER}) |
||||||
|
same => n,Set(CDR(actualdst)=${CALLEE}) |
||||||
|
same => n,Set(CDR(realcall)=1) |
||||||
|
same => n,Verbose(${CALL_OPERATION}: ${CALLER} -> ${CALLEE}) |
||||||
|
same => n,Set(LIMIT_PLAYAUDIO_CALLER=no,LIMIT_PLAYAUDIO_CALLEE=yes) |
||||||
|
same => n,Set(LIMIT_TIMEOUT_FILE=custom/call-expired) |
||||||
|
same => n,Set(LIMIT_WARNING_FILE=custom/call-expiring-soon) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
|
||||||
|
[pre-call] |
||||||
|
exten => s,1,Gosub(volume-setup,s,1) |
||||||
|
same => n,Gosub(record-start,s,1) |
||||||
|
same => n,Set(CDR(realcall)=2) |
||||||
|
same => n,Set(CDR(startedat)=${EPOCH}) |
||||||
|
same => n,Set(CDR(actualdisposition)=ANSWERED) |
||||||
|
same => n,Set(CDR(actualdst2)=${CALLERID(num)}) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
|
||||||
|
[pre-out-call] |
||||||
|
exten => s,1,Gosub(volume-setup,s,1) |
||||||
|
same => n,Gosub(record-start,s,1) |
||||||
|
same => n,Set(CDR(realcall)=2) |
||||||
|
same => n,Set(CDR(startedat)=${EPOCH}) |
||||||
|
same => n,Return() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; 1.1. Place an inbound call into a single queue |
||||||
|
[inbound-queued] |
||||||
|
exten => s,1,Gosub(pre-any,s,1(${ARG1},INBOUND-QUEUED)) |
||||||
|
same => n,Verbose(DS ${DEVICE_STATE(PJSIP/${CALLEE})}) |
||||||
|
same => n,GosubIf($["${DEVICE_STATE(PJSIP/${CALLEE})}" = "BUSY"]?inbound-queued-busy,s,1) |
||||||
|
same => n,GosubIf($["${DEVICE_STATE(PJSIP/${CALLEE})}" = "INUSE"]?inbound-queued-busy,s,1) |
||||||
|
same => n,GosubIf($["${DEVICE_STATE(PJSIP/${CALLEE})}" = "RINGINUSE"]?inbound-queued-busy,s,1) |
||||||
|
same => n,GosubIf($["${DEVICE_STATE(PJSIP/${CALLEE})}" = "RINGING"]?inbound-queued-busy,s,1) |
||||||
|
same => n,GosubIf($["${DEVICE_STATE(PJSIP/${CALLEE})}" = "UNAVAILABLE"]?inbound-queued-unavail,s,1) |
||||||
|
same => n,Queue(queue-${CALLEE},inrt,,,,,,pre-call) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
; 1.2. Callee is busy, play a message (if appropriate) and place it into a single queue |
||||||
|
[inbound-queued-busy] |
||||||
|
exten => s,1,Verbose(QUEUED BUSY) |
||||||
|
same => n,GotoIf($["${CALLEE}"="9"]?busy-le) |
||||||
|
same => n,GotoIf($[ $["${CALLEE}"="6"] | $["${CALLEE}"="8"] | $["${CALLEE}"="10"] | $["${CALLEE}"="12"] ]?busy-spb) |
||||||
|
same => n,Background(custom/this-busy-ask-redirect,m,,inbound-queued-select-busy)) |
||||||
|
same => n,Queue(queue-${CALLEE},inrt,,,,,,pre-call) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
same => n(busy-le),Playback(custom/this-busy-le) |
||||||
|
same => n,Queue(queue-${CALLEE},inrt,,,,,,pre-call) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
same => n(busy-spb),Playback(custom/this-busy-spb) |
||||||
|
same => n,Queue(queue-${CALLEE},inrt,,,,,,pre-call) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; 1.25. Callee is not available, play a message and place it into a single queue |
||||||
|
;same => n,GosubIf($[ $["${CALLEE}"="6"] | $["${CALLEE}"="8"] | $["${CALLEE}"="10"] | $["${CALLEE}"="12"] ]?inbound-queued-unavail-spb,s,1) |
||||||
|
|
||||||
|
[inbound-queued-unavail] |
||||||
|
exten => s,1,Verbose(QUEUED UNAVAIL) |
||||||
|
same => n,GosubIf($["${CALLEE}"="9"]?inbound-queued-unavail-le,s,1) |
||||||
|
same => n,Playback(custom/this-unavail-will-redirect) |
||||||
|
same => n,Set(CDR(ivr)=3) |
||||||
|
same => n,Queue(queue-some-${CALLEE},inrt,,,,,,pre-call) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
; 1.25.1 LE callee is not available, play a message and hang up |
||||||
|
[inbound-queued-unavail-le] |
||||||
|
exten => s,1,Playback(custom/this-unavail-le) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
; 1.25.2 SPB callee is not available, play a message and hang up |
||||||
|
;[inbound-queued-unavail-spb] |
||||||
|
;exten => s,1,Playback(custom/this-unavail-spb) |
||||||
|
; same => n,Wait(0.5) |
||||||
|
; same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; 1.3. Caller has requested to join a "some" queue, place it there |
||||||
|
[inbound-queued-to-some] |
||||||
|
exten => s,1,Verbose(QUEUE TO SOME) |
||||||
|
same => n,Set(CDR(ivr)=3) |
||||||
|
same => n,Queue(queue-some-${CALLEE},inrt,,,,,,pre-call) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
; 1.1.1. Allow callers to exit from a background playback to dial some |
||||||
|
[inbound-queued-select-busy] |
||||||
|
exten => 1,1,Gosub(inbound-queued-to-some,s,1) |
||||||
|
|
||||||
|
|
||||||
|
; 1.2.1. Allow callers to exit from a queue to dial some |
||||||
|
; Invalid DTMF keypresses get redirected back to inbound queue |
||||||
|
[inbound-queued-inqueue-busy] |
||||||
|
exten => 1,1,Gosub(inbound-queued-to-some,s,1) |
||||||
|
exten => i,1,Gosub(inbound-queued,s,1(${CALLEE})) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; Inbound calls from Multifon trunk to LE endpoint (9) |
||||||
|
[inbound-multifon] |
||||||
|
exten => _Z.,1,Gosub(inbound-queued,s,1(9)) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
; Inbound calls from Dom.ru 222003 endpoint directly to ext 1 |
||||||
|
[inbound-domru-3] |
||||||
|
exten => _Z.,1,Gosub(inbound-queued,s,1(1)) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
; Inbound calls from Dom.ru 222004 endpoint to IVR |
||||||
|
[inbound-domru-4] |
||||||
|
exten => _Z.,1,Gosub(pre-any,s,1(IVR,INBOUND)) |
||||||
|
same => n,Gosub(ivr,s,1) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
; Inbound calls from Smart SPB trunk to SPB IVR |
||||||
|
[inbound-smart-spb] |
||||||
|
exten => _Z.,1,Gosub(pre-any,s,1(IVR,INBOUND)) |
||||||
|
same => n,Gosub(ivr-spb,s,1) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; Outbound calls from all local endpoints |
||||||
|
[outbound] |
||||||
|
exten => _Z,hint,PJSIP/${EXTEN} |
||||||
|
exten => _ZX,hint,PJSIP/${EXTEN} |
||||||
|
exten => _Z,1,Gosub(outbound-internal,s,1(${EXTEN})) |
||||||
|
exten => _ZX,1,Gosub(outbound-internal,s,1(${EXTEN})) |
||||||
|
|
||||||
|
|
||||||
|
exten => _7XXXXXXXXXX,1,GosubIf($[ $["${CALLERID(num)}"="6"] | $["${CALLERID(num)}"="8"] | $["${CALLERID(num)}"="10"] | $["${CALLERID(num)}"="12"] ]?outbound-external,s,1(8${EXTEN:1}):outbound-external,s,1(+${EXTEN})) |
||||||
|
exten => _8XXXXXXXXXX,1,GosubIf($[ $["${CALLERID(num)}"="6"] | $["${CALLERID(num)}"="8"] | $["${CALLERID(num)}"="10"] | $["${CALLERID(num)}"="12"] ]?outbound-external,s,1(${EXTEN}):outbound-external,s,1(+7${EXTEN:1})) |
||||||
|
exten => _+7XXXXXXXXXX,1,GosubIf($[ $["${CALLERID(num)}"="6"] | $["${CALLERID(num)}"="8"] | $["${CALLERID(num)}"="10"] | $["${CALLERID(num)}"="12"] ]?outbound-external,s,1(8${EXTEN:2}):outbound-external,s,1(${EXTEN})) |
||||||
|
exten => _9XXXXXXXXX,1,GosubIf($[ $["${CALLERID(num)}"="6"] | $["${CALLERID(num)}"="8"] | $["${CALLERID(num)}"="10"] | $["${CALLERID(num)}"="12"] ]?outbound-external,s,1(8${EXTEN}):outbound-external,s,1(+7${EXTEN})) |
||||||
|
exten => _XXXXXX,1,Gosub(outbound-external,s,1(+78332${EXTEN})) |
||||||
|
exten => _XXXXXXX,1,GosubIf($[ $["${CALLERID(num)}"="6"] | $["${CALLERID(num)}"="8"] | $["${CALLERID(num)}"="10"] | $["${CALLERID(num)}"="12"] ]?outbound-external,s,1(${EXTEN})) |
||||||
|
include => service |
||||||
|
include => catchall |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; Internal calls |
||||||
|
[outbound-internal] |
||||||
|
exten => s,1,Gosub(pre-any,s,1(${ARG1},INTERNAL)) |
||||||
|
|
||||||
|
same => n,GotoIf($["${CALLERID(number)}" = "${ARG1}"]?busy,s,1) ; dialing the same extension as caller |
||||||
|
same => n,GotoIf($["${DEVICE_STATE(PJSIP/${ARG1})}" = "INVALID"]?invalid-ext,s,1) ; extension is invalid |
||||||
|
same => n,GotoIf($["${DEVICE_STATE(PJSIP/${ARG1})}" = "UNAVAILABLE"]?offline-ext,s,1) ; extension is valid but offline |
||||||
|
|
||||||
|
same => n,Dial(PJSIP/${ARG1},900,girtTL(3600000:60000)U(pre-out-call)) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[outbound-external] |
||||||
|
exten => s,1,Gosub(pre-any,s,1(${ARG1},OUTBOUND)) |
||||||
|
|
||||||
|
same => n,GosubIf($[${CALLERID(num)} = 9]?outbound-multifon,s,1(${ARG1})) |
||||||
|
same => n,GosubIf($[${CALLERID(num)} = 6]?outbound-smart-spb,s,1(${ARG1})) |
||||||
|
same => n,GosubIf($[${CALLERID(num)} = 8]?outbound-smart-spb,s,1(${ARG1})) |
||||||
|
same => n,GosubIf($[${CALLERID(num)} = 10]?outbound-smart-spb,s,1(${ARG1})) |
||||||
|
same => n,GosubIf($[${CALLERID(num)} = 12]?outbound-smart-spb,s,1(${ARG1})) |
||||||
|
same => n,Gosub(outbound-domru,s,1(${ARG1})) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[outbound-multifon] |
||||||
|
exten => s,1,Dial(PJSIP/${ARG1}@endpoint-multifon,900,irTL(3600000:60000)U(pre-out-call)) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
[outbound-domru] |
||||||
|
exten => s,1,Dial(PJSIP/${ARG1}@endpoint-domru-4,900,irTL(3600000:60000)U(pre-out-call)) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
[outbound-smart-spb] |
||||||
|
exten => s,1,Dial(PJSIP/${ARG1}@endpoint-smart-spb,900,irTL(3600000:60000)U(pre-out-call)) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[service] |
||||||
|
; Simple ring test |
||||||
|
exten => 001,1,Ringing() |
||||||
|
same => n,Wait(20) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
; Hello World playback |
||||||
|
exten => 002,1,Answer(250) |
||||||
|
same => n,Playback(hello-world) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
; Echo test |
||||||
|
exten => 003,1,Answer(250) |
||||||
|
same => n,Playback(demo-echotest) |
||||||
|
same => n,Echo |
||||||
|
same => n,Playback(demo-echodone) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
; Internal IVR |
||||||
|
exten => 004,1,Answer(250) |
||||||
|
same => n,Gosub(ivr,s,1) |
||||||
|
same => n,Wait(0.5) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
; Congestion test |
||||||
|
exten => 005,1,Congestion() |
||||||
|
same => n,Wait(20) |
||||||
|
same => n,Hangup() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[catchall] |
||||||
|
exten => _X.,1,Gosub(invalid-ext,s,1) ; go to invalid extension macro on all extensions |
||||||
|
exten => i,1,Gosub(invalid-ext,s,1) ; same, but with invalid extensions |
@ -0,0 +1,5 @@ |
|||||||
|
- name: add backup dirs to collected backup dirs |
||||||
|
set_fact: |
||||||
|
collected_backup_dirs: "{{ (collected_backup_dirs | d([])) + |
||||||
|
([backup_items] if backup_items is string else backup_items) }}" |
||||||
|
when: backup_items is defined and ((backup_items | type_debug == 'list') or backup_items is string) |
@ -0,0 +1,8 @@ |
|||||||
|
- name: add to backup plan |
||||||
|
include_tasks: add.yml |
||||||
|
when: function is defined and function == 'add' |
||||||
|
|
||||||
|
|
||||||
|
- name: setup backups |
||||||
|
include_tasks: setup.yml |
||||||
|
when: function is defined and function == 'setup' |
@ -0,0 +1,31 @@ |
|||||||
|
- name: notify that backups are not supported |
||||||
|
debug: |
||||||
|
msg: backup host is missing, will not set up backups |
||||||
|
when: services.backup is not mapping |
||||||
|
|
||||||
|
|
||||||
|
- name: install restic with custom configuration |
||||||
|
block: |
||||||
|
- include_role: |
||||||
|
name: restic |
||||||
|
vars: |
||||||
|
backup: "{{ backup_cfg }}" |
||||||
|
|
||||||
|
when: services.backup is mapping and backup_cfg is mapping |
||||||
|
|
||||||
|
|
||||||
|
- name: install restic with default configuration |
||||||
|
block: |
||||||
|
- include_role: |
||||||
|
name: restic |
||||||
|
vars: |
||||||
|
backup: |
||||||
|
dirs: "{{ collected_backup_dirs }}" |
||||||
|
password: "{{ backup_password }}" |
||||||
|
tags: automated |
||||||
|
filter: |
||||||
|
- "*.log" |
||||||
|
- "node_modules" |
||||||
|
- ".npm" |
||||||
|
|
||||||
|
when: services.backup is mapping and backup_cfg is not defined and backup_password is defined |
@ -0,0 +1,52 @@ |
|||||||
|
blocky_user: blocky |
||||||
|
blocky_group: blocky |
||||||
|
blocky_dir: /opt/blocky |
||||||
|
blocky_conf_dir: /etc/blocky |
||||||
|
blocky_conf_file: "{{ blocky_conf_dir }}/blocky.yml" |
||||||
|
|
||||||
|
blocky_tls_ecc384_cert: "{{ blocky_conf_dir }}/ecc384.crt" |
||||||
|
blocky_tls_ecc384_key: "{{ blocky_conf_dir }}/ecc384.key" |
||||||
|
|
||||||
|
blocky_port: 9000 |
||||||
|
blocky_enable_dot: yes |
||||||
|
|
||||||
|
blocky_default_groups: |
||||||
|
- selector: default |
||||||
|
groups: |
||||||
|
- all |
||||||
|
|
||||||
|
blocky_default_config: |
||||||
|
port: 53 |
||||||
|
bootstrapDns: 1.1.1.1 |
||||||
|
logLevel: warn |
||||||
|
logTimestamp: no |
||||||
|
upstreamTimeout: 4s |
||||||
|
|
||||||
|
httpPort: "127.0.0.1:{{ blocky_port }}" |
||||||
|
|
||||||
|
prometheus: |
||||||
|
enable: "{{ host_metrics }}" |
||||||
|
|
||||||
|
caching: |
||||||
|
maxTime: 8h |
||||||
|
maxItemsCount: 15000 |
||||||
|
prefetchMaxItemsCount: 1000 |
||||||
|
|
||||||
|
upstream: |
||||||
|
default: |
||||||
|
- tcp-tls:anycast.censurfridns.dk:853 |
||||||
|
- tcp-tls:dns.quad9.net:853 |
||||||
|
- tcp-tls:one.one.one.one:853 |
||||||
|
- tcp-tls:dns.digitale-gesellschaft.ch:853 |
||||||
|
|
||||||
|
blocking: |
||||||
|
blackLists: |
||||||
|
all: |
||||||
|
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts |
||||||
|
- https://block.energized.pro/extensions/regional/formats/hosts |
||||||
|
- https://block.energized.pro/bluGo/formats/hosts |
||||||
|
whiteLists: |
||||||
|
all: |
||||||
|
- https://raw.githubusercontent.com/anudeepND/whitelist/master/domains/whitelist.txt |
||||||
|
refreshPeriod: 8h |
||||||
|
blockTTL: 5m |
@ -0,0 +1,4 @@ |
|||||||
|
- name: restart blocky |
||||||
|
service: |
||||||
|
name: blocky |
||||||
|
state: restarted |
@ -0,0 +1,185 @@ |
|||||||
|
- name: import internal tld resolver vars if internal nameserver is present |
||||||
|
include_vars: |
||||||
|
file: internal.yml |
||||||
|
when: services.internal_ns is defined |
||||||
|
|
||||||
|
|
||||||
|
- name: import ipv6 disable snippet |
||||||
|
include_vars: |
||||||
|
file: disable_ipv6.yml |
||||||
|
hash_behaviour: merge |
||||||
|
when: blocky_disable_ipv6 | d(false) == true |
||||||
|
|
||||||
|
|
||||||
|
- name: import tls support |
||||||
|
include_vars: |
||||||
|
file: tls.yml |
||||||
|
hash_behaviour: merge |
||||||
|
when: host_tls and blocky_enable_dot |
||||||
|
|
||||||
|
|
||||||
|
- name: set blocky_cfg |
||||||
|
set_fact: |
||||||
|
blocky_cfg: "{{ blocky_default_config | d({}) | combine(blocky_config | d({}), recursive=true) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: install dependencies |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- libcap |
||||||
|
- libc6-compat |
||||||
|
|
||||||
|
|
||||||
|
- name: create user and group |
||||||
|
include_tasks: tasks/create_user.yml |
||||||
|
vars: |
||||||
|
user: |
||||||
|
name: "{{ blocky_user }}" |
||||||
|
group: "{{ blocky_group }}" |
||||||
|
dir: "{{ blocky_dir }}" |
||||||
|
notify: restart blocky |
||||||
|
|
||||||
|
|
||||||
|
- name: create directories |
||||||
|
file: |
||||||
|
path: "{{ item }}" |
||||||
|
state: directory |
||||||
|
mode: 0755 |
||||||
|
owner: "{{ blocky_user }}" |
||||||
|
group: "{{ blocky_group }}" |
||||||
|
notify: restart blocky |
||||||
|
loop: |
||||||
|
- "{{ blocky_conf_dir }}" |
||||||
|
- "{{ blocky_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: get and extract latest version of blocky |
||||||
|
include_tasks: tasks/get_lastversion.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
name: 0xERR0R/blocky |
||||||
|
location: github |
||||||
|
assets: yes |
||||||
|
asset_filter: 'Linux_x86_64.tar.gz$' |
||||||
|
file: "{{ blocky_dir }}/last_version" |
||||||
|
extract: "{{ blocky_dir }}" |
||||||
|
user: "{{ blocky_user }}" |
||||||
|
group: "{{ blocky_group }}" |
||||||
|
notify: restart blocky |
||||||
|
|
||||||
|
|
||||||
|
- name: template config file |
||||||
|
template: |
||||||
|
src: blocky.j2 |
||||||
|
dest: "{{ blocky_conf_file }}" |
||||||
|
force: yes |
||||||
|
mode: 0400 |
||||||
|
owner: "{{ blocky_user }}" |
||||||
|
group: "{{ blocky_group }}" |
||||||
|
lstrip_blocks: yes |
||||||
|
notify: restart blocky |
||||||
|
|
||||||
|
|
||||||
|
- name: template init script |
||||||
|
template: |
||||||
|
src: init.j2 |
||||||
|
dest: /etc/init.d/blocky |
||||||
|
force: yes |
||||||
|
mode: "+x" |
||||||
|
notify: restart blocky |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure blocky binary has executable bit set |
||||||
|
file: |
||||||
|
path: "{{ blocky_dir }}/blocky" |
||||||
|
mode: "+x" |
||||||
|
|
||||||
|
|
||||||
|
- name: add cap_net_bind_service to blocky executable |
||||||
|
community.general.capabilities: |
||||||
|
path: "{{ blocky_dir }}/blocky" |
||||||
|
capability: cap_net_bind_service+ep |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
|
||||||
|
- name: install and configure nginx |
||||||
|
include_role: |
||||||
|
name: nginx |
||||||
|
vars: |
||||||
|
nginx: |
||||||
|
servers: |
||||||
|
- conf: nginx_server |
||||||
|
certs: "{{ host_tls }}" |
||||||
|
external_tld: "{{ host_tld }}" |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: get certificate file type |
||||||
|
stat: |
||||||
|
path: /etc/nginx/tls/ecc384.crt |
||||||
|
register: stat |
||||||
|
|
||||||
|
|
||||||
|
- name: copy nginx ecc384 certificate to blocky dir |
||||||
|
copy: |
||||||
|
src: "/etc/nginx/tls/{{ item.src }}" |
||||||
|
dest: "{{ item.dest }}" |
||||||
|
force: yes |
||||||
|
mode: 0400 |
||||||
|
owner: "{{ blocky_user }}" |
||||||
|
group: "{{ blocky_group }}" |
||||||
|
remote_src: yes |
||||||
|
loop: |
||||||
|
- src: ecc384.crt |
||||||
|
dest: "{{ blocky_tls_ecc384_cert }}" |
||||||
|
- src: ecc384.key |
||||||
|
dest: "{{ blocky_tls_ecc384_key }}" |
||||||
|
when: not (stat.stat.islnk is defined and stat.stat.islnk) |
||||||
|
|
||||||
|
|
||||||
|
- name: create symlinks |
||||||
|
file: |
||||||
|
path: "{{ item.dest }}" |
||||||
|
src: "/etc/nginx/tls/{{ item.src }}" |
||||||
|
state: link |
||||||
|
force: yes |
||||||
|
loop: |
||||||
|
- src: ecc384.crt |
||||||
|
dest: "{{ blocky_tls_ecc384_cert }}" |
||||||
|
- src: ecc384.key |
||||||
|
dest: "{{ blocky_tls_ecc384_key }}" |
||||||
|
when: stat.stat.islnk is defined and stat.stat.islnk |
||||||
|
|
||||||
|
when: host_tls and blocky_enable_dot |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ blocky_conf_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: add prometheus metric target |
||||||
|
include_role: |
||||||
|
name: prometheus |
||||||
|
vars: |
||||||
|
function: add_target |
||||||
|
target: |
||||||
|
name: blocky |
||||||
|
scheme: "{{ host_protocol }}" |
||||||
|
when: host_metrics |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: enable and start blocky |
||||||
|
service: |
||||||
|
name: blocky |
||||||
|
enabled: yes |
||||||
|
state: started |
@ -0,0 +1,7 @@ |
|||||||
|
{%- set mappings = blocky_default_mappings | items2dict(key_name='tld', value_name='resolver') -%} |
||||||
|
{%- set conditional = { 'conditional': { 'mapping': mappings }} -%} |
||||||
|
|
||||||
|
{%- set groups = blocky_default_groups | items2dict(key_name='selector', value_name='groups') -%} |
||||||
|
{%- set clientGroupsBlock = { 'blocking': { 'clientGroupsBlock': groups }} -%} |
||||||
|
|
||||||
|
{{- blocky_cfg | combine(clientGroupsBlock, recursive=true) | combine(conditional, recursive=true) | to_nice_yaml(indent=2, width=512) }} |
@ -0,0 +1,19 @@ |
|||||||
|
#!/sbin/openrc-run |
||||||
|
|
||||||
|
name="blocky" |
||||||
|
command="{{ blocky_dir }}/blocky" |
||||||
|
command_args="--config {{ blocky_conf_file | quote }}" |
||||||
|
directory="{{ blocky_dir }}" |
||||||
|
command_user="{{ blocky_user }}:{{ blocky_group }}" |
||||||
|
pidfile="/var/run/blocky.pid" |
||||||
|
command_background=true |
||||||
|
start_stop_daemon_args="--stdout-logger logger --stderr-logger logger" |
||||||
|
|
||||||
|
depend() { |
||||||
|
need net |
||||||
|
use dns |
||||||
|
} |
||||||
|
|
||||||
|
start_pre() { |
||||||
|
setcap 'cap_net_bind_service=+ep' {{ (blocky_dir ~ '/blocky') | quote }} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
location / { |
||||||
|
return 404; |
||||||
|
} |
||||||
|
|
||||||
|
location /dns-query { |
||||||
|
proxy_pass http://127.0.0.1:{{ blocky_port }}; |
||||||
|
proxy_set_header Connection ""; |
||||||
|
} |
||||||
|
|
||||||
|
{% if host_metrics -%} |
||||||
|
location /metrics { |
||||||
|
proxy_pass http://127.0.0.1:{{ blocky_port }}; |
||||||
|
allow {{ int_net }}; |
||||||
|
deny all; |
||||||
|
} |
||||||
|
{%- endif %} |
@ -0,0 +1,4 @@ |
|||||||
|
blocky_default_config: |
||||||
|
filtering: |
||||||
|
queryTypes: |
||||||
|
- AAAA |
@ -0,0 +1,7 @@ |
|||||||
|
blocky_default_mappings: |
||||||
|
- tld: "{{ int_tld }}" |
||||||
|
resolver: "{%- if services.internal_ns is mapping -%}\ |
||||||
|
{{- hostvars[services.internal_ns.hostname]['ansible_host'] -}}\ |
||||||
|
{%- else -%}\ |
||||||
|
{{- hostvars | dict2items | selectattr('key', 'in', services.internal_ns | map(attribute='hostname')) | map(attribute='value') | list | map(attribute='ansible_host') | list | join(',') -}}\ |
||||||
|
{%- endif -%}" |
@ -0,0 +1,4 @@ |
|||||||
|
blocky_default_config: |
||||||
|
tlsPort: 853 |
||||||
|
certFile: "{{ blocky_tls_ecc384_cert }}" |
||||||
|
keyFile: "{{ blocky_tls_ecc384_key }}" |
@ -0,0 +1,27 @@ |
|||||||
|
ca_key_types: |
||||||
|
- { name: rsa2048, type: RSA, size: 2048 } |
||||||
|
- { name: ecc384, type: ECC, curve: secp384r1, digest: sha384 } |
||||||
|
|
||||||
|
ca_key_names: "{{ ca_key_types | map(attribute='name') | list }}" |
||||||
|
|
||||||
|
ca_default_items: |
||||||
|
- { type: ecc384 } |
||||||
|
- { type: rsa2048 } |
||||||
|
|
||||||
|
ca_dir: /etc/ca |
||||||
|
|
||||||
|
ca_rp: root- |
||||||
|
ca_ip: inter- |
||||||
|
ca_crt_ext: crt |
||||||
|
ca_key_ext: key |
||||||
|
ca_csr_ext: csr |
||||||
|
ca_pfx_ext: pfx |
||||||
|
|
||||||
|
# when to start to reissue certs |
||||||
|
ca_reissue_period: 8w |
||||||
|
|
||||||
|
ca_options: {} |
||||||
|
|
||||||
|
crl_last_update_time: +8w |
||||||
|
crl_next_update_time: +24w |
||||||
|
crl_dir: /opt/crl |
@ -0,0 +1,227 @@ |
|||||||
|
- include_tasks: prepare_item.yml |
||||||
|
|
||||||
|
|
||||||
|
- name: define combined options |
||||||
|
set_fact: |
||||||
|
ca_combined: "{{ ca_options | d({}) | combine(item) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: define cert parameters |
||||||
|
set_fact: |
||||||
|
key_path: "{%- if item.key is defined -%}{{ item.key }}\ |
||||||
|
{%- else -%}{{ ca_combined.path ~ '/' ~ kt.name ~ '.' ~ ca_key_ext }}\ |
||||||
|
{%- endif -%}" |
||||||
|
|
||||||
|
cert_path: "{%- if item.cert is defined -%}{{ item.cert }}\ |
||||||
|
{%- else -%}{{ ca_combined.path ~ '/' ~ kt.name ~ '.' ~ ca_crt_ext }}\ |
||||||
|
{%- endif -%}" |
||||||
|
|
||||||
|
use_acme: "{{ ca_combined.acme | d(has_acme | d(false)) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: define tld and presets |
||||||
|
set_fact: |
||||||
|
ca_tld: "{{ ca_combined.tld | d(host_tld) }}" |
||||||
|
ca_presets: |
||||||
|
web: |
||||||
|
cn: FQDN |
||||||
|
eku: ['clientAuth', 'serverAuth'] |
||||||
|
ku: ['digitalSignature', 'keyEncipherment', 'keyAgreement'] |
||||||
|
san: FQDN |
||||||
|
psh: |
||||||
|
cn: FQDN |
||||||
|
eku: ['serverAuth'] |
||||||
|
ku: ['digitalSignature', 'keyEncipherment', 'keyAgreement'] |
||||||
|
san: FQDN |
||||||
|
|
||||||
|
|
||||||
|
- name: select a preset |
||||||
|
set_fact: |
||||||
|
ca_preset: > |
||||||
|
{% if item.preset is defined -%}{{ ca_presets[item.preset] }} |
||||||
|
{%- elif ca_options.preset is defined -%}{{ ca_presets[ca_options.preset] }} |
||||||
|
{%- else -%}{{ None }} |
||||||
|
{%- endif %} |
||||||
|
|
||||||
|
|
||||||
|
- name: generate private key |
||||||
|
community.crypto.openssl_privatekey: |
||||||
|
path: "{{ key_path }}" |
||||||
|
size: "{{ kt.size | d(omit) }}" |
||||||
|
curve: "{{ kt.curve | d(omit) }}" |
||||||
|
type: "{{ kt.type }}" |
||||||
|
backup: yes |
||||||
|
force: no |
||||||
|
format: pkcs8 |
||||||
|
format_mismatch: convert |
||||||
|
regenerate: never |
||||||
|
mode: "{{ k_mode | d(omit) }}" |
||||||
|
owner: "{{ k_owner | d(omit) }}" |
||||||
|
group: "{{ k_group | d(omit) }}" |
||||||
|
notify: "{{ ca_options.notify | d(omit) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: generate in-memory csr request for private key |
||||||
|
community.crypto.openssl_csr_pipe: |
||||||
|
basic_constraints: |
||||||
|
- 'CA:FALSE' |
||||||
|
basic_constraints_critical: yes |
||||||
|
digest: "{{ kt.digest | d(omit) }}" |
||||||
|
key_usage_critical: yes |
||||||
|
privatekey_path: "{{ key_path }}" |
||||||
|
|
||||||
|
common_name: "{%- if item.cn is defined -%}{{ item.cn }}\ |
||||||
|
{%- elif ca_options.cn is defined -%}{{ ca_options.cn }}\ |
||||||
|
{%- elif ca_preset.cn == 'FQDN' -%}{{ host_name ~ '.' ~ ca_tld }}\ |
||||||
|
{%- elif ca_preset.cn is defined -%}{{ ca_preset.cn }}\ |
||||||
|
{%- endif -%}" |
||||||
|
|
||||||
|
extended_key_usage: "{%- if item.eku is defined -%}{{ item.eku }}\ |
||||||
|
{%- elif ca_options.eku is defined -%}{{ ca_options.eku }}\ |
||||||
|
{%- elif ca_preset.eku is defined -%}{{ ca_preset.eku }}\ |
||||||
|
{%- endif -%}" |
||||||
|
|
||||||
|
key_usage: "{%- if item.ku is defined -%}{{ item.ku }}\ |
||||||
|
{%- elif ca_options.ku is defined -%}{{ ca_options.ku }}\ |
||||||
|
{%- elif ca_preset.ku is defined -%}{{ ca_preset.ku }}\ |
||||||
|
{%- else -%}{{ ['digitalSignature', 'keyEncipherment', 'keyAgreement'] }}\ |
||||||
|
{%- endif -%}" |
||||||
|
|
||||||
|
subject_alt_name: "{%- if item.san is defined -%}{{ item.san }}\ |
||||||
|
{%- elif ca_options.san is defined -%}{{ ca_options.san }}\ |
||||||
|
{%- elif item.cn is defined -%}{{ ['DNS:' ~ item.cn] }}\ |
||||||
|
{%- elif ca_options.cn is defined -%}{{ ['DNS:' ~ ca_options.cn] }}\ |
||||||
|
{%- elif ca_preset.san == 'FQDN' -%}{{ ['DNS:' ~ host_name ~ '.' ~ ca_tld] }}\ |
||||||
|
{%- elif ca_preset.san is defined -%}{{ ca_preset.san }}\ |
||||||
|
{%- endif -%}" |
||||||
|
|
||||||
|
ocsp_must_staple: "{{ (has_acme | d(false)) and (ca_options.ocsp_must_staple | d(false)) }}" |
||||||
|
register: csr |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
|
||||||
|
- name: check if cert already exists |
||||||
|
stat: |
||||||
|
path: "{{ cert_path }}" |
||||||
|
register: cert_exists |
||||||
|
|
||||||
|
|
||||||
|
- name: slurp cert if exists |
||||||
|
slurp: |
||||||
|
src: "{{ cert_path }}" |
||||||
|
when: cert_exists.stat.exists |
||||||
|
register: cert |
||||||
|
|
||||||
|
|
||||||
|
- name: check if the cert validity period is about to expire |
||||||
|
community.crypto.x509_certificate_info: |
||||||
|
content: "{{ cert.content | b64decode }}" |
||||||
|
valid_at: |
||||||
|
reissue_period: "+{%- if has_acme | d(false) == true -%}45d\ |
||||||
|
{%- else -%}{{ ca_reissue_period | d('8w') }}\ |
||||||
|
{%- endif -%}" |
||||||
|
when: cert_exists.stat.exists |
||||||
|
register: cert_info |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: generate certificate on ca |
||||||
|
community.crypto.x509_certificate_pipe: |
||||||
|
content: "{{ (cert.content | b64decode) if cert_exists.stat.exists else omit }}" |
||||||
|
csr_content: "{{ csr.csr }}" |
||||||
|
provider: ownca |
||||||
|
ownca_not_after: "{{ item.duration | d('+365d') }}" |
||||||
|
ownca_not_before: -1d |
||||||
|
ownca_digest: "{{ kt.digest | d(omit) }}" |
||||||
|
ownca_path: "{{ ca_dir }}/{{ ca_ip }}{{ kt.name }}.{{ ca_crt_ext }}" |
||||||
|
ownca_privatekey_path: "{{ ca_dir }}/{{ ca_ip }}{{ kt.name }}.{{ ca_key_ext }}" |
||||||
|
ownca_privatekey_passphrase: "{{ ca_pk_inter_password }}" |
||||||
|
force: "{{ cert_exists.stat.exists and not cert_info.valid_at.reissue_period }}" |
||||||
|
register: cert |
||||||
|
delegate_to: "{{ services.ca.hostname }}" |
||||||
|
notify: "{{ ca_options.notify | d(omit) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: save new cert if it was changed |
||||||
|
copy: |
||||||
|
dest: "{{ cert_path }}" |
||||||
|
content: "{{ cert.certificate }}" |
||||||
|
mode: "{{ k_mode | d(omit) }}" |
||||||
|
owner: "{{ k_owner | d(omit) }}" |
||||||
|
group: "{{ k_group | d(omit) }}" |
||||||
|
follow: "{{ (ca_options | combine(item)).follow_symlinks | d(omit) }}" |
||||||
|
when: cert is changed |
||||||
|
notify: "{{ ca_options.notify | d(omit) }}" |
||||||
|
|
||||||
|
when: has_acme | d(false) == false |
||||||
|
|
||||||
|
|
||||||
|
- name: generate acme certificate |
||||||
|
include_tasks: gen_acme.yml |
||||||
|
when: has_acme | d(false) == true |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: slurp certificate |
||||||
|
slurp: |
||||||
|
src: "{{ cert_path }}" |
||||||
|
register: cert |
||||||
|
|
||||||
|
- name: complete certificate chain |
||||||
|
community.crypto.certificate_complete_chain: |
||||||
|
input_chain: "{{ ((cert.content | b64decode).split('\n\n'))[0] }}" |
||||||
|
root_certificates: /etc/ssl/certs |
||||||
|
register: chain |
||||||
|
|
||||||
|
- name: save chain to file |
||||||
|
copy: |
||||||
|
dest: "{{ item.chain }}" |
||||||
|
content: | |
||||||
|
{% set result = chain.complete_chain %} |
||||||
|
{% set _ = result.pop(0) %} |
||||||
|
{{ result | join('') }} |
||||||
|
mode: "{{ k_mode | d(omit) }}" |
||||||
|
owner: "{{ k_owner | d(omit) }}" |
||||||
|
group: "{{ k_group | d(omit) }}" |
||||||
|
follow: "{{ (ca_options | combine(item)).follow_symlinks | d(omit) }}" |
||||||
|
notify: "{{ ca_options.notify | d(omit) }}" |
||||||
|
|
||||||
|
when: item.chain is string |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: slurp intermediate from ca |
||||||
|
slurp: |
||||||
|
src: "{{ ca_dir }}/{{ ca_ip }}{{ kt.name }}.{{ ca_crt_ext }}" |
||||||
|
register: inter |
||||||
|
delegate_to: "{{ services.ca.hostname }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: add intermediate cert if requested |
||||||
|
blockinfile: |
||||||
|
block: "{{ inter.content | b64decode }}" |
||||||
|
insertafter: EOF |
||||||
|
marker: "" |
||||||
|
path: "{{ cert_path }}" |
||||||
|
notify: "{{ ca_options.notify | d(omit) }}" |
||||||
|
|
||||||
|
when: (use_acme | d(false) == false) and (cert is changed) and ((ca_options | combine(item)).concat_inter | d(true) == true) |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: slurp root from ca |
||||||
|
slurp: |
||||||
|
src: "{{ ca_dir }}/{{ ca_rp }}{{ kt.name }}.{{ ca_crt_ext }}" |
||||||
|
register: root |
||||||
|
delegate_to: "{{ services.ca.hostname }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: add root cert if requested |
||||||
|
blockinfile: |
||||||
|
block: "{{ root.content | b64decode }}" |
||||||
|
insertafter: EOF |
||||||
|
marker: "" |
||||||
|
path: "{{ cert_path }}" |
||||||
|
notify: "{{ ca_options.notify | d(omit) }}" |
||||||
|
|
||||||
|
when: (use_acme | d(false) == false) and (cert is changed) and ((ca_options | combine(item)).concat_root | d(false) == true) |
@ -0,0 +1,44 @@ |
|||||||
|
- include_tasks: prepare_item.yml |
||||||
|
|
||||||
|
|
||||||
|
- name: slurp root from ca |
||||||
|
slurp: |
||||||
|
src: "{{ ca_dir }}/{{ ca_rp }}{{ kt.name }}.{{ ca_crt_ext }}" |
||||||
|
register: root |
||||||
|
delegate_to: "{{ services.ca.hostname }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: copy root to memory |
||||||
|
set_fact: |
||||||
|
"root_{{ kt.name }}": "{{ root.content | b64decode }}" |
||||||
|
when: (ca_options | combine(item)).memory | d(false) == true |
||||||
|
|
||||||
|
|
||||||
|
- name: copy root to remote node |
||||||
|
copy: |
||||||
|
dest: "{%- if item.path is defined -%}{{ item.path }}\ |
||||||
|
{%- else -%}{{ ca_options.path ~ '/' ~ ca_rp ~ kt.name ~ '.' ~ ca_crt_ext }}\ |
||||||
|
{%- endif -%}" |
||||||
|
content: "{{ root.content | b64decode }}" |
||||||
|
mode: "{{ k_mode | d(omit) }}" |
||||||
|
owner: "{{ k_owner | d(omit) }}" |
||||||
|
group: "{{ k_group | d(omit) }}" |
||||||
|
when: (ca_options | combine(item)).path is defined |
||||||
|
|
||||||
|
|
||||||
|
- name: copy root to system storage |
||||||
|
block: |
||||||
|
- name: ensure ca-certificates is installed |
||||||
|
package: |
||||||
|
name: ca-certificates |
||||||
|
|
||||||
|
- name: upload root cert to user cert storage |
||||||
|
copy: |
||||||
|
dest: "/usr/local/share/ca-certificates/{{ ca_rp }}{{ kt.name }}.{{ ca_crt_ext }}" |
||||||
|
content: "{{ root.content | b64decode }}" |
||||||
|
|
||||||
|
- name: update ca certificates |
||||||
|
command: /usr/sbin/update-ca-certificates |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
when: (ca_options | combine(item)).system | d(false) == true |
@ -0,0 +1,18 @@ |
|||||||
|
- block: |
||||||
|
- name: check if acme main account exists |
||||||
|
community.crypto.acme_account_info: |
||||||
|
account_key_src: "{{ ca_dir ~ '/acme-main.' ~ ca_key_ext }}" |
||||||
|
account_key_passphrase: "{{ ca_acme_account_key_password }}" |
||||||
|
acme_directory: "{{ ca_acme_endpoint | d('https://acme-v02.api.letsencrypt.org/directory') }}" |
||||||
|
acme_version: "{{ ca_acme_version | d(2) }}" |
||||||
|
register: acme_info |
||||||
|
delegate_to: "{{ services.ca.hostname }}" |
||||||
|
|
||||||
|
- name: determine acme support |
||||||
|
set_fact: |
||||||
|
has_acme: "{{ acme_info is defined and acme_info.exists and acme_info.account.status == 'valid' and (acme_disable | d(false) == false) }}" |
||||||
|
|
||||||
|
rescue: |
||||||
|
- name: revert has_acme |
||||||
|
set_fact: |
||||||
|
has_acme: false |
@ -0,0 +1,86 @@ |
|||||||
|
- name: define some acme parameters |
||||||
|
set_fact: |
||||||
|
acme_staging: "{{ (ca_options | d({}) | combine(item)).acme_staging | d(false) }}" |
||||||
|
acme_upgrade_int_ca: "{{ cert_info is defined and ((cert_info.ocsp_uri is not defined) or (cert_info.ocsp_uri == None)) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: determine if acme cert generation will be forced |
||||||
|
set_fact: |
||||||
|
acme_forced: "{{ acme_upgrade_int_ca or (always_update_acme is defined) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: slurp account key from ca |
||||||
|
slurp: |
||||||
|
src: "{{ ca_dir ~ '/acme-' ~ ('staging' if acme_staging == true else 'main') ~ '.' ~ ca_key_ext }}" |
||||||
|
register: acme_account_key |
||||||
|
delegate_to: "{{ services.ca.hostname }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: define args for acme certificate generation |
||||||
|
set_fact: |
||||||
|
acme_common_args: |
||||||
|
account_key_content: "{{ acme_account_key.content | b64decode }}" |
||||||
|
account_key_passphrase: "{{ ca_acme_account_key_password }}" |
||||||
|
acme_directory: "{%- if (acme_staging == false) or (acme_staging == None) -%}{{ ca_acme_endpoint | d('https://acme-v02.api.letsencrypt.org/directory') }}\ |
||||||
|
{%- else -%}{{ ca_acme_staging_endpoint | d('https://acme-staging-v02.api.letsencrypt.org/directory') }}\ |
||||||
|
{%- endif -%}" |
||||||
|
acme_version: "{{ ca_acme_version | d(2) }}" |
||||||
|
acme_extra_args: |
||||||
|
challenge: dns-01 |
||||||
|
csr_content: "{{ csr.csr }}" |
||||||
|
fullchain_dest: "{{ cert_path if ((ca_options | d({}) | combine(item)).concat_inter | d(true) == true) else omit }}" |
||||||
|
dest: "{{ cert_path if ((ca_options | d({}) | combine(item)).concat_inter | d(true) == false) else omit }}" |
||||||
|
modify_account: no |
||||||
|
remaining_days: 45 |
||||||
|
force: "{{ acme_forced }}" |
||||||
|
terms_agreed: yes |
||||||
|
|
||||||
|
|
||||||
|
- name: generate acme challenge request |
||||||
|
community.crypto.acme_certificate: |
||||||
|
args: "{{ acme_common_args | combine(acme_extra_args) }}" |
||||||
|
register: challenge |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: unset challenge_records |
||||||
|
set_fact: |
||||||
|
challenge_records: "{{ [] }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: fill challenge records |
||||||
|
set_fact: |
||||||
|
challenge_records: "{{ challenge_records + [{ |
||||||
|
'name': item2.key | regex_search('(.*).' ~ (tld | regex_escape()), '\\1') | first, |
||||||
|
'type': 'TXT', |
||||||
|
'value': item2.value[0] |
||||||
|
}] }}" |
||||||
|
loop: "{{ challenge['challenge_data_dns'] | dict2items }}" |
||||||
|
loop_control: |
||||||
|
loop_var: item2 |
||||||
|
|
||||||
|
|
||||||
|
- include_tasks: gen_acme_include.yml |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: revoke cert if it already exists |
||||||
|
community.crypto.acme_certificate_revoke: |
||||||
|
certificate: "{{ cert_path }}" |
||||||
|
revoke_reason: 4 |
||||||
|
args: "{{ acme_common_args }}" |
||||||
|
when: (cert_exists is defined) and cert_exists.stat.exists and not acme_upgrade_int_ca |
||||||
|
|
||||||
|
rescue: |
||||||
|
- debug: |
||||||
|
msg: failed to revoke certificate, ignoring |
||||||
|
|
||||||
|
|
||||||
|
- name: finalize acme challenge request |
||||||
|
community.crypto.acme_certificate: |
||||||
|
data: "{{ challenge }}" |
||||||
|
args: "{{ acme_common_args | combine(acme_extra_args) }}" |
||||||
|
notify: "{{ ca_options.notify | d(omit) }}" |
||||||
|
|
||||||
|
when: (challenge.cert_days is not defined) or (challenge.cert_days < 45) or acme_forced |
@ -0,0 +1,7 @@ |
|||||||
|
- name: add records to external ns |
||||||
|
include_role: |
||||||
|
name: external_ns |
||||||
|
vars: |
||||||
|
nse_items: "{{ challenge_records }}" |
||||||
|
nse_function: add_records |
||||||
|
nse_instant: true |
@ -0,0 +1,74 @@ |
|||||||
|
- name: define dh param dict |
||||||
|
set_fact: |
||||||
|
dh: "{{ {'remote_gen': true, 'size': 2048, 'backup': false} | combine(dh_params | d({})) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: check if dhparam file exists |
||||||
|
stat: |
||||||
|
path: "{{ dh.path | mandatory }}" |
||||||
|
register: res |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: ensure cryptography toolkit is installed |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- alpine: py3-cryptography |
||||||
|
debian: python3-cryptography |
||||||
|
when: dh.remote_gen == false |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: wait until ca becomes available |
||||||
|
wait_for_connection: |
||||||
|
timeout: 10 |
||||||
|
|
||||||
|
- name: create temporary file for dh params |
||||||
|
tempfile: |
||||||
|
state: file |
||||||
|
register: tf |
||||||
|
|
||||||
|
delegate_to: "{{ services.ca.hostname }}" |
||||||
|
when: dh.remote_gen == true |
||||||
|
|
||||||
|
|
||||||
|
- name: generate dh params |
||||||
|
community.crypto.openssl_dhparam: |
||||||
|
path: "{%- if dh.remote_gen == false -%}{{ dh.path | mandatory }}\ |
||||||
|
{%- else -%}{{ tf.path }}\ |
||||||
|
{%- endif -%}" |
||||||
|
size: "{{ dh.size }}" |
||||||
|
backup: "{{ dh.backup }}" |
||||||
|
mode: "{{ (dh.mode | d('0400')) if (dh.remote_gen == false) else '0400' }}" |
||||||
|
owner: "{{ (dh.owner | d(omit)) if (dh.remote_gen == false) else omit }}" |
||||||
|
group: "{{ (dh.group | d(omit)) if (dh.remote_gen == false) else omit }}" |
||||||
|
return_content: "{{ dh.remote_gen == true }}" |
||||||
|
delegate_to: "{{ inventory_hostname if (dh.remote_gen == false) else services.ca.hostname }}" |
||||||
|
notify: "{{ dh.notify | d(omit) }}" |
||||||
|
register: dh_result |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: remove temporary file |
||||||
|
file: |
||||||
|
path: "{{ tf.path }}" |
||||||
|
state: absent |
||||||
|
delegate_to: "{{ services.ca.hostname }}" |
||||||
|
|
||||||
|
- name: copy dh result to remote node |
||||||
|
copy: |
||||||
|
content: "{{ dh_result.dhparams }}" |
||||||
|
dest: "{{ dh.path | mandatory }}" |
||||||
|
mode: "{{ dh.mode | d('0400') }}" |
||||||
|
owner: "{{ dh.owner | d(omit) }}" |
||||||
|
group: "{{ dh.group | d(omit) }}" |
||||||
|
|
||||||
|
when: dh.remote_gen == true |
||||||
|
|
||||||
|
when: (not res.stat.exists) or (dh.remote_gen == false) |
||||||
|
|
||||||
|
|
||||||
|
- name: unset dh param dict |
||||||
|
set_fact: |
||||||
|
dh: "{{ {} }}" |
@ -0,0 +1,154 @@ |
|||||||
|
- name: ensure cryptography toolkit is installed |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- alpine: py3-cryptography |
||||||
|
debian: python3-cryptography |
||||||
|
|
||||||
|
|
||||||
|
- name: early check to ensure ca variables are defined |
||||||
|
fail: |
||||||
|
msg: "\"{{ item }}\" is not defined" |
||||||
|
when: item is not defined |
||||||
|
loop: |
||||||
|
- ca_dir |
||||||
|
- ca_key_types |
||||||
|
- ca_rp |
||||||
|
- ca_ip |
||||||
|
- ca_crt_ext |
||||||
|
- ca_csr_ext |
||||||
|
- ca_key_ext |
||||||
|
|
||||||
|
|
||||||
|
- name: create ca directories |
||||||
|
file: |
||||||
|
path: "{{ ca_dir }}" |
||||||
|
state: directory |
||||||
|
mode: 0700 |
||||||
|
|
||||||
|
|
||||||
|
- name: generate root private keys |
||||||
|
community.crypto.openssl_privatekey: |
||||||
|
path: "{{ ca_dir }}/{{ ca_rp }}{{ item.name }}.{{ ca_key_ext }}" |
||||||
|
size: "{{ item.size | d(omit) }}" |
||||||
|
curve: "{{ item.curve | d(omit) }}" |
||||||
|
type: "{{ item.type }}" |
||||||
|
backup: yes |
||||||
|
cipher: auto |
||||||
|
force: no |
||||||
|
format: pkcs8 |
||||||
|
format_mismatch: convert |
||||||
|
passphrase: "{{ ca_pk_password }}" |
||||||
|
regenerate: never |
||||||
|
mode: 0600 |
||||||
|
loop: "{{ ca_key_types }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: generate csr requests for all root keys |
||||||
|
community.crypto.openssl_csr: |
||||||
|
path: "{{ ca_dir }}/{{ ca_rp }}{{ item.name }}.{{ ca_csr_ext }}" |
||||||
|
basic_constraints: |
||||||
|
- 'CA:TRUE' |
||||||
|
basic_constraints_critical: yes |
||||||
|
common_name: "{{ org }} Root CA ({{ item.type | upper }})" |
||||||
|
digest: "{{ item.digest | d(omit) }}" |
||||||
|
key_usage: |
||||||
|
- keyCertSign |
||||||
|
- cRLSign |
||||||
|
key_usage_critical: yes |
||||||
|
privatekey_path: "{{ ca_dir }}/{{ ca_rp }}{{ item.name }}.{{ ca_key_ext }}" |
||||||
|
privatekey_passphrase: "{{ ca_pk_password }}" |
||||||
|
use_common_name_for_san: no |
||||||
|
mode: 0600 |
||||||
|
loop: "{{ ca_key_types }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: generate root certificates |
||||||
|
community.crypto.x509_certificate: |
||||||
|
path: "{{ ca_dir }}/{{ ca_rp }}{{ item.name }}.{{ ca_crt_ext }}" |
||||||
|
csr_path: "{{ ca_dir }}/{{ ca_rp }}{{ item.name }}.{{ ca_csr_ext }}" |
||||||
|
privatekey_path: "{{ ca_dir }}/{{ ca_rp }}{{ item.name }}.{{ ca_key_ext }}" |
||||||
|
privatekey_passphrase: "{{ ca_pk_password }}" |
||||||
|
provider: selfsigned |
||||||
|
selfsigned_not_after: "{{ ca_root_valid_until | mandatory }}" |
||||||
|
selfsigned_digest: "{{ item.digest | d(omit) }}" |
||||||
|
mode: 0600 |
||||||
|
loop: "{{ ca_key_types }}" |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- name: generate inter private keys |
||||||
|
community.crypto.openssl_privatekey: |
||||||
|
path: "{{ ca_dir }}/{{ ca_ip }}{{ item.name }}.{{ ca_key_ext }}" |
||||||
|
size: "{{ item.size | d(omit) }}" |
||||||
|
curve: "{{ item.curve | d(omit) }}" |
||||||
|
type: "{{ item.type }}" |
||||||
|
backup: yes |
||||||
|
cipher: auto |
||||||
|
force: no |
||||||
|
format: pkcs8 |
||||||
|
format_mismatch: convert |
||||||
|
passphrase: "{{ ca_pk_inter_password }}" |
||||||
|
regenerate: never |
||||||
|
mode: 0600 |
||||||
|
loop: "{{ ca_key_types }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: generate csr requests for all inter keys |
||||||
|
community.crypto.openssl_csr: |
||||||
|
path: "{{ ca_dir }}/{{ ca_ip }}{{ item.name }}.{{ ca_csr_ext }}" |
||||||
|
basic_constraints: |
||||||
|
- 'CA:TRUE' |
||||||
|
- 'pathlen:0' |
||||||
|
basic_constraints_critical: yes |
||||||
|
common_name: "{{ org }} Intermediate CA ({{ item.type | upper }})" |
||||||
|
digest: "{{ item.digest | d(omit) }}" |
||||||
|
key_usage: |
||||||
|
- digitalSignature |
||||||
|
- keyCertSign |
||||||
|
- cRLSign |
||||||
|
key_usage_critical: yes |
||||||
|
privatekey_path: "{{ ca_dir }}/{{ ca_ip }}{{ item.name }}.{{ ca_key_ext }}" |
||||||
|
privatekey_passphrase: "{{ ca_pk_inter_password }}" |
||||||
|
use_common_name_for_san: no |
||||||
|
|
||||||
|
crl_distribution_points: |
||||||
|
- full_name: "URI:http://crl.{{ int_tld }}/{{ item.name }}.crl" |
||||||
|
crl_issuer: "URI:http://crl.{{ int_tld }}" |
||||||
|
name_constraints_permitted: |
||||||
|
- "DNS:{{ tld }}" |
||||||
|
- "email:{{ tld }}" |
||||||
|
name_constraints_excluded: |
||||||
|
- "IP:0.0.0.0/0" |
||||||
|
mode: 0600 |
||||||
|
loop: "{{ ca_key_types }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: generate inter certificates |
||||||
|
community.crypto.x509_certificate: |
||||||
|
path: "{{ ca_dir }}/{{ ca_ip }}{{ item.name }}.{{ ca_crt_ext }}" |
||||||
|
csr_path: "{{ ca_dir }}/{{ ca_ip }}{{ item.name }}.{{ ca_csr_ext }}" |
||||||
|
privatekey_path: "{{ ca_dir }}/{{ ca_ip }}{{ item.name }}.{{ ca_key_ext }}" |
||||||
|
privatekey_passphrase: "{{ ca_pk_inter_password }}" |
||||||
|
provider: ownca |
||||||
|
ownca_not_after: "{{ ca_inter_valid_until | mandatory }}" |
||||||
|
ownca_digest: "{{ item.digest | d(omit) }}" |
||||||
|
ownca_path: "{{ ca_dir }}/{{ ca_rp }}{{ item.name }}.{{ ca_crt_ext }}" |
||||||
|
ownca_privatekey_path: "{{ ca_dir }}/{{ ca_rp }}{{ item.name }}.{{ ca_key_ext }}" |
||||||
|
ownca_privatekey_passphrase: "{{ ca_pk_password }}" |
||||||
|
mode: 0600 |
||||||
|
loop: "{{ ca_key_types }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: install acme |
||||||
|
include_tasks: install_acme.yml |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ ca_dir }}" |
@ -0,0 +1,39 @@ |
|||||||
|
- name: select key type for acme |
||||||
|
set_fact: |
||||||
|
kt: "{{ ca_key_types | selectattr('name', 'equalto', ca_acme_account_key_type | d('ecc384')) | list | first }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: generate acme account keys |
||||||
|
community.crypto.openssl_privatekey: |
||||||
|
path: "{{ ca_dir ~ '/acme-' ~ item ~ '.' ~ ca_key_ext }}" |
||||||
|
size: "{{ kt.size | d(omit) }}" |
||||||
|
curve: "{{ kt.curve | d(omit) }}" |
||||||
|
type: "{{ kt.type }}" |
||||||
|
backup: yes |
||||||
|
cipher: auto |
||||||
|
force: no |
||||||
|
format: pkcs8 |
||||||
|
format_mismatch: convert |
||||||
|
passphrase: "{{ ca_acme_account_key_password }}" |
||||||
|
regenerate: never |
||||||
|
mode: 0600 |
||||||
|
loop: |
||||||
|
- main |
||||||
|
- staging |
||||||
|
|
||||||
|
|
||||||
|
- name: create acme accounts |
||||||
|
community.crypto.acme_account: |
||||||
|
account_key_src: "{{ ca_dir ~ '/acme-' ~ item ~ '.' ~ ca_key_ext }}" |
||||||
|
account_key_passphrase: "{{ ca_acme_account_key_password }}" |
||||||
|
acme_directory: "{%- if item == 'main' -%}{{ ca_acme_endpoint | d('https://acme-v02.api.letsencrypt.org/directory') }}\ |
||||||
|
{%- else -%}{{ ca_acme_staging_endpoint | d('https://acme-staging-v02.api.letsencrypt.org/directory') }}\ |
||||||
|
{%- endif -%}" |
||||||
|
acme_version: "{{ ca_acme_version | d(2) }}" |
||||||
|
contact: |
||||||
|
- "mailto:{{ maintainer_email | d('admin@' ~ tld) }}" |
||||||
|
state: present |
||||||
|
terms_agreed: yes |
||||||
|
loop: |
||||||
|
- main |
||||||
|
- staging |
@ -0,0 +1,51 @@ |
|||||||
|
- name: ca installation |
||||||
|
include_tasks: install.yml |
||||||
|
when: function == 'install' |
||||||
|
|
||||||
|
|
||||||
|
- name: install roots |
||||||
|
include_tasks: add_root.yml |
||||||
|
loop: "{{ ca_default_items if (ca_roots is not defined) or (ca_roots == None) or ((ca_roots | length) == 0) else ca_roots }}" |
||||||
|
when: function == 'roots' |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: wait until ca becomes available |
||||||
|
wait_for_connection: |
||||||
|
timeout: 10 |
||||||
|
delegate_to: "{{ services.ca.hostname }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: check if acme can be used |
||||||
|
include_tasks: check_acme.yml |
||||||
|
|
||||||
|
|
||||||
|
- name: process roots if no acme will be used |
||||||
|
include_tasks: add_root.yml |
||||||
|
loop: "{{ ca_default_items if (ca_roots is not defined) or (ca_roots == None) or ((ca_roots | length) == 0) else ca_roots }}" |
||||||
|
when: not has_acme |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure cryptography toolkit is installed |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- alpine: py3-cryptography |
||||||
|
debian: python3-cryptography |
||||||
|
|
||||||
|
|
||||||
|
- name: process certificates |
||||||
|
include_tasks: add_cert.yml |
||||||
|
loop: "{{ ca_default_items if (ca_certs is not defined) or (ca_certs == None) or ((ca_certs | length) == 0) else ca_certs }}" |
||||||
|
|
||||||
|
when: function == 'certs' |
||||||
|
|
||||||
|
|
||||||
|
- name: generate dhparams |
||||||
|
include_tasks: gen_dhparam.yml |
||||||
|
when: (function == 'dhparam' or function == 'dhparams') |
||||||
|
|
||||||
|
|
||||||
|
- name: check acme availability |
||||||
|
include_tasks: check_acme.yml |
||||||
|
when: function == 'check_acme' |
@ -0,0 +1,17 @@ |
|||||||
|
- name: select key type |
||||||
|
set_fact: |
||||||
|
kt: "{{ ca_key_types | selectattr('name', 'equalto', item.type) | list | first }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: fail if key type is empty |
||||||
|
fail: |
||||||
|
msg: "key type must be one of: {{ ca_key_names | join(', ') }}" |
||||||
|
when: (kt | length) == 0 |
||||||
|
|
||||||
|
|
||||||
|
- name: set preferred mode, owner and group |
||||||
|
set_fact: |
||||||
|
k_mode: "{{ (ca_options | d({}) | combine(item)).mode | d(omit) }}" |
||||||
|
k_owner: "{{ (ca_options | d({}) | combine(item)).owner | d(omit) }}" |
||||||
|
k_group: "{{ (ca_options | d({}) | combine(item)).group | d(omit) }}" |
||||||
|
k_none: yes |
@ -0,0 +1,24 @@ |
|||||||
|
cdr_user: cdr |
||||||
|
cdr_group: cdr |
||||||
|
cdr_dir: /opt/cdr |
||||||
|
cdr_port: 3000 |
||||||
|
|
||||||
|
cdr_default_config: |
||||||
|
port: "{{ cdr_port }}" |
||||||
|
|
||||||
|
db_type: pg |
||||||
|
db_host: "{{ database_host }}" |
||||||
|
db_user: "{{ database_user }}" |
||||||
|
db_password: "{{ database_pass }}" |
||||||
|
db_database: "{{ database_name }}" |
||||||
|
db_table: cdr |
||||||
|
|
||||||
|
record_dir: /opt/recordings |
||||||
|
record_pretty_names: yes |
||||||
|
|
||||||
|
ami_enable: yes |
||||||
|
ami_host: 127.0.0.1 |
||||||
|
|
||||||
|
originate_enable: yes |
||||||
|
originate_context: outbound |
||||||
|
originate_timeout: 30 |
@ -0,0 +1,4 @@ |
|||||||
|
- name: restart cdr |
||||||
|
service: |
||||||
|
name: cdr |
||||||
|
state: restarted |
@ -0,0 +1,141 @@ |
|||||||
|
- name: set cdr_cfg |
||||||
|
set_fact: |
||||||
|
cdr_cfg: "{{ cdr_default_config | d({}) | combine(cdr_config | d({}), recursive=true) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: install dependencies |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- nodejs |
||||||
|
- npm |
||||||
|
|
||||||
|
|
||||||
|
- name: add extra cname record |
||||||
|
include_role: |
||||||
|
name: ns |
||||||
|
vars: |
||||||
|
function: add_records |
||||||
|
ns_add_default_record: no |
||||||
|
ns_records: |
||||||
|
- name: cdr |
||||||
|
type: CNAME |
||||||
|
value: "{{ host_fqdn }}" |
||||||
|
when: "inventory_hostname != 'cdr'" |
||||||
|
|
||||||
|
|
||||||
|
- name: create user and group |
||||||
|
include_tasks: tasks/create_user.yml |
||||||
|
vars: |
||||||
|
user: |
||||||
|
name: "{{ cdr_user }}" |
||||||
|
group: "{{ cdr_group }}" |
||||||
|
dir: "{{ cdr_dir }}" |
||||||
|
notify: restart cdr |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure cdr dir exists |
||||||
|
file: |
||||||
|
path: "{{ cdr_dir }}" |
||||||
|
state: directory |
||||||
|
owner: "{{ cdr_user }}" |
||||||
|
group: "{{ cdr_group }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure recordings dir exists |
||||||
|
file: |
||||||
|
path: "{{ cdr_cfg.record_dir }}" |
||||||
|
state: directory |
||||||
|
|
||||||
|
|
||||||
|
- name: get source-mark status |
||||||
|
stat: |
||||||
|
path: "{{ cdr_dir }}/source-mark" |
||||||
|
register: source_mark |
||||||
|
|
||||||
|
|
||||||
|
- name: pause if source-mark is missing |
||||||
|
pause: |
||||||
|
prompt: source-mark is missing, source code has to be manually uploaded |
||||||
|
when: source_mark.stat.exists == false |
||||||
|
|
||||||
|
|
||||||
|
- name: create source-mark |
||||||
|
file: |
||||||
|
path: "{{ cdr_dir }}/source-mark" |
||||||
|
state: touch |
||||||
|
modification_time: preserve |
||||||
|
access_time: preserve |
||||||
|
|
||||||
|
|
||||||
|
- name: template env file |
||||||
|
template: |
||||||
|
src: env.j2 |
||||||
|
dest: "{{ cdr_dir }}/.env" |
||||||
|
force: yes |
||||||
|
owner: "{{ cdr_user }}" |
||||||
|
group: "{{ cdr_group }}" |
||||||
|
lstrip_blocks: yes |
||||||
|
notify: restart cdr |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure app script has executable bit set |
||||||
|
file: |
||||||
|
path: "{{ cdr_dir }}/app.js" |
||||||
|
mode: "+x" |
||||||
|
|
||||||
|
|
||||||
|
- name: install npm dependencies |
||||||
|
npm: |
||||||
|
path: "{{ cdr_dir }}" |
||||||
|
no_optional: yes |
||||||
|
ignore_scripts: yes |
||||||
|
production: yes |
||||||
|
become: yes |
||||||
|
become_user: "{{ cdr_user }}" |
||||||
|
become_method: su |
||||||
|
become_flags: '-s /bin/ash' |
||||||
|
notify: restart cdr |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
|
||||||
|
- name: template init script |
||||||
|
template: |
||||||
|
src: init.j2 |
||||||
|
dest: /etc/init.d/cdr |
||||||
|
force: yes |
||||||
|
mode: "+x" |
||||||
|
notify: restart cdr |
||||||
|
|
||||||
|
|
||||||
|
- name: install and configure nginx |
||||||
|
include_role: |
||||||
|
name: nginx |
||||||
|
vars: |
||||||
|
nginx: |
||||||
|
servers: |
||||||
|
- conf: nginx_server |
||||||
|
override_server_name: cdr |
||||||
|
certs: "{{ host_tls }}" |
||||||
|
domains: |
||||||
|
- "cdr.{{ host_tld }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ cdr_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: enable and start cdr |
||||||
|
service: |
||||||
|
name: cdr |
||||||
|
enabled: yes |
||||||
|
state: started |
@ -0,0 +1,3 @@ |
|||||||
|
{% for option in (cdr_cfg | d({}) | dict2items) -%} |
||||||
|
{{ option.key | upper }}={{ option.value | quote }} |
||||||
|
{% endfor -%} |
@ -0,0 +1,14 @@ |
|||||||
|
#!/sbin/openrc-run |
||||||
|
|
||||||
|
name="$SVCNAME" |
||||||
|
directory="{{ cdr_dir }}" |
||||||
|
command="node {{ cdr_dir }}/app.js" |
||||||
|
command_user="{{ cdr_user }}:{{ cdr_group }}" |
||||||
|
pidfile="/var/run/$SVCNAME.pid" |
||||||
|
supervisor="supervise-daemon" |
||||||
|
respawn_max=0 |
||||||
|
|
||||||
|
depend() { |
||||||
|
need net |
||||||
|
use dns |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
set_real_ip_from 10.0.0.0/8; |
||||||
|
real_ip_header X-Real-IP; |
||||||
|
real_ip_recursive on; |
||||||
|
|
||||||
|
location / { |
||||||
|
proxy_pass http://127.0.0.1:{{ cdr_port }}; |
||||||
|
proxy_http_version 1.1; |
||||||
|
proxy_set_header X-Real-IP $remote_addr; |
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||||
|
proxy_set_header X-Forwarded-Proto $scheme; |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
- name: set combined cert info |
||||||
|
set_fact: |
||||||
|
combined: "{{ (common | d({})) | combine(cert | d({}), recursive=true) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: generate certificate through acme-dns |
||||||
|
include_role: |
||||||
|
name: acme |
||||||
|
vars: |
||||||
|
acme_id: "{{ cert.id | d(host_name ~ ('-ecc' if (combined.ecc | d(false) == true) else '')) }}" |
||||||
|
acme_cert: "{{ cert.cert }}" |
||||||
|
acme_key: "{{ cert.key }}" |
||||||
|
acme_chain: "{{ cert.chain | d(None) }}" |
||||||
|
acme_cert_single: "{{ cert.cert_single | d(None) }}" |
||||||
|
acme_ecc: "{{ combined.ecc | d(false) }}" |
||||||
|
acme_stapling: no |
||||||
|
acme_notify: "{{ combined.notify | d(None) }}" |
||||||
|
acme_owner: "{{ combined.owner | d(None) }}" |
||||||
|
acme_group: "{{ combined.group | d(None) }}" |
||||||
|
acme_post_hook: "{{ combined.post_hook | d(None) }}" |
||||||
|
acme_hostname: "{{ combined.hostname | d(None) }}" |
||||||
|
acme_tld: "{{ combined.tld | d(None) }}" |
||||||
|
acme_fqdn: "{{ combined.fqdn | d(None) }}" |
||||||
|
acme_hosts: "{{ combined.hosts | d(None) }}" |
@ -0,0 +1,46 @@ |
|||||||
|
- name: set combined cert info |
||||||
|
set_fact: |
||||||
|
combined: "{{ cert | combine(common | d({}), recursive=true) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: clear san list |
||||||
|
set_fact: |
||||||
|
san_list: "{{ [] }}" |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: build san list |
||||||
|
set_fact: |
||||||
|
san_list: "{{ (san_list | d([])) + ['DNS:' ~ (item.fqdn | d((item.hostname | d(host_name)) ~ '.' ~ (item.tld | d(host_tld))))] }}" |
||||||
|
loop: "{{ cert.hosts }}" |
||||||
|
when: (cert.hosts is defined) and (cert.hosts | type_debug == 'list') |
||||||
|
|
||||||
|
|
||||||
|
- name: generate certificate through external ns |
||||||
|
include_role: |
||||||
|
name: ca |
||||||
|
vars: |
||||||
|
function: certs |
||||||
|
ca_options: |
||||||
|
mode: '0400' |
||||||
|
owner: "{{ combined.owner | d(None) }}" |
||||||
|
group: "{{ combined.group | d(None) }}" |
||||||
|
concat_inter: yes |
||||||
|
preset: web |
||||||
|
acme: yes |
||||||
|
ocsp_must_staple: "{{ combined.stapling | d(false) }}" |
||||||
|
notify: "{{ combined.notify | d(None) }}" |
||||||
|
ca_certs: |
||||||
|
- type: "{{ 'ecc384' if (combined.ecc | d(false) == true) else 'rsa2048' }}" |
||||||
|
cert: "{{ cert.cert }}" |
||||||
|
key: "{{ cert.key }}" |
||||||
|
cn: "{% if cert.hosts is defined and cert.hosts | type_debug == 'list' -%}\ |
||||||
|
{{ cert.hosts[0].fqdn | d((cert.hosts[0].hostname | d(host_name)) ~ '.' ~ (cert.hosts[0].tld | d(host_tld))) }}\ |
||||||
|
{%- else -%}\ |
||||||
|
{{ combined.fqdn | d((combined.hostname | d(host_name)) ~ '.' ~ (combined.tld | d(host_tld))) }}\ |
||||||
|
{%- endif -%}" |
||||||
|
san: "{% if san_list | length > 0 -%}\ |
||||||
|
{{ san_list }}\ |
||||||
|
{%- else -%}\ |
||||||
|
{{ 'DNS:' ~ (combined.fqdn | d((combined.hostname | d(host_name)) ~ '.' ~ (combined.tld | d(host_tld)))) }}\ |
||||||
|
{%- endif -%}" |
@ -0,0 +1,2 @@ |
|||||||
|
- fail: |
||||||
|
msg: deployment of certs through internal CA is not implemented |
@ -0,0 +1,41 @@ |
|||||||
|
- name: validate cert parameter |
||||||
|
fail: |
||||||
|
msg: certs variable must be a dict or a list |
||||||
|
when: (certs is not defined) or ((certs is not mapping) and (certs | type_debug != 'list')) |
||||||
|
|
||||||
|
|
||||||
|
- name: validate common parameter |
||||||
|
fail: |
||||||
|
msg: common variable must be a dict |
||||||
|
when: (common is defined) and (common is not mapping) |
||||||
|
|
||||||
|
|
||||||
|
- name: validate certificates |
||||||
|
include_tasks: validate.yml |
||||||
|
loop: "{{ certs if (certs | type_debug == 'list') else [certs] }}" |
||||||
|
loop_control: |
||||||
|
loop_var: cert |
||||||
|
|
||||||
|
|
||||||
|
- name: process certificates with acme dns |
||||||
|
include_tasks: acme_dns.yml |
||||||
|
loop: "{{ certs if (certs | type_debug == 'list') else [certs] }}" |
||||||
|
loop_control: |
||||||
|
loop_var: cert |
||||||
|
when: services.acme_dns is defined |
||||||
|
|
||||||
|
|
||||||
|
- name: process certificates with standalone dns |
||||||
|
include_tasks: external_ns.yml |
||||||
|
loop: "{{ certs if (certs | type_debug == 'list') else [certs] }}" |
||||||
|
loop_control: |
||||||
|
loop_var: cert |
||||||
|
when: (services.external_ns is defined) and (services.acme_dns is not defined) |
||||||
|
|
||||||
|
|
||||||
|
- name: process certificates with internal ca |
||||||
|
include_tasks: internal_ca.yml |
||||||
|
loop: "{{ certs if (certs | type_debug == 'list') else [certs] }}" |
||||||
|
loop_control: |
||||||
|
loop_var: cert |
||||||
|
when: (services.ca is defined) and (services.external_ns is not defined) and (services.acme_dns is not defined) |
@ -0,0 +1,46 @@ |
|||||||
|
- name: validate mandatory parameters |
||||||
|
fail: |
||||||
|
msg: some mandatory parameters in cert variable are missing or invalid |
||||||
|
when: (cert is not defined) or (cert is not mapping) or |
||||||
|
(cert.key is not string) or (cert.cert is not string) |
||||||
|
|
||||||
|
|
||||||
|
- name: validate optional parameters |
||||||
|
fail: |
||||||
|
msg: some optional parameters in cert variable are missing or invalid |
||||||
|
when: ((cert.ca is defined) and (cert.ca is not string)) or |
||||||
|
((cert.id is defined) and (cert.id is not string)) or |
||||||
|
((cert.ecc is defined) and (cert.ecc is not boolean)) or |
||||||
|
((cert.fqdn is defined) and (cert.fqdn is not string)) or |
||||||
|
((cert.tld is defined) and (cert.tld is not string)) or |
||||||
|
((cert.hostname is defined) and (cert.hostname is not string)) or |
||||||
|
((cert.hosts is defined) and (cert.hosts | type_debug != 'list')) or |
||||||
|
((cert.tld is defined) and (cert.tld is not string)) or |
||||||
|
((cert.stapling is defined) and (cert.stapling is not boolean)) or |
||||||
|
((cert.post_hook is defined) and (cert.post_hook is not string)) or |
||||||
|
((cert.notify is defined) and (cert.notify is not string)) or |
||||||
|
((cert.owner is defined) and (cert.owner is not string)) or |
||||||
|
((cert.group is defined) and (cert.group is not string)) |
||||||
|
|
||||||
|
|
||||||
|
- name: validate parameter combinations |
||||||
|
fail: |
||||||
|
msg: parameters are defined in an invalid combination |
||||||
|
when: ((cert.fqdn is defined) and (cert.hosts is defined)) or |
||||||
|
((cert.tld is defined) and (cert.hosts is defined)) or |
||||||
|
((cert.hostname is defined) and (cert.hosts is defined)) or |
||||||
|
((cert.fqdn is defined) and (cert.tld is defined)) or |
||||||
|
((cert.fqdn is defined) and (cert.hostname is defined)) |
||||||
|
|
||||||
|
|
||||||
|
- name: validate hosts |
||||||
|
fail: |
||||||
|
msg: host parameters are invalid or are defined in an invalid combination |
||||||
|
when: ((host.fqdn is defined) and (host.fqdn is not string)) or |
||||||
|
((host.tld is defined) and (host.tld is not string)) or |
||||||
|
((host.hostname is defined) and (host.hostname is not string)) or |
||||||
|
((host.fqdn is defined) and (host.tld is defined)) or |
||||||
|
((host.fqdn is defined) and (host.hostname is defined)) |
||||||
|
loop: "{{ cert.hosts }}" |
||||||
|
loop_control: |
||||||
|
loop_var: host |
@ -0,0 +1,72 @@ |
|||||||
|
clamav_user: clamav |
||||||
|
clamav_group: clamav |
||||||
|
|
||||||
|
clamav_conf_dir: /etc/clamav |
||||||
|
clamav_db_dir: /opt/clamav |
||||||
|
|
||||||
|
clamav_conf_file: "{{ clamav_conf_dir }}/clamd.conf" |
||||||
|
clamav_freshclam_conf_file: "{{ clamav_conf_dir }}/freshclam.conf" |
||||||
|
clamav_milter_conf_file: "{{ clamav_conf_dir }}/clamav-milter.conf" |
||||||
|
|
||||||
|
clamav_socket: /run/clamav/clamd.sock |
||||||
|
|
||||||
|
clamav_max_file_size: "{{ mail_server.max_mail_size_bytes | d('25M') }}" |
||||||
|
|
||||||
|
|
||||||
|
clamav_default_config: |
||||||
|
clamav: |
||||||
|
log_syslog: yes |
||||||
|
log_facility: LOG_LOCAL0 |
||||||
|
extended_detection_info: yes |
||||||
|
pid_file: /run/clamav/clamd.pid |
||||||
|
database_directory: "{{ clamav_db_dir }}" |
||||||
|
local_socket: "{{ clamav_socket }}" |
||||||
|
local_socket_mode: 660 |
||||||
|
stream_max_length: "{{ clamav_max_file_size }}" |
||||||
|
self_check: 3600 |
||||||
|
concurrent_database_reload: no |
||||||
|
user: "{{ clamav_user }}" |
||||||
|
detect_p_u_a: yes |
||||||
|
heuristic_scan_precedence: no |
||||||
|
alert_encrypted: yes |
||||||
|
alert_encrypted_archive: yes |
||||||
|
alert_encrypted_doc: yes |
||||||
|
max_scan_time: 30000 |
||||||
|
max_file_size: "{{ clamav_max_file_size }}" |
||||||
|
max_recursion: 12 |
||||||
|
alert_exceeds_max: yes |
||||||
|
bytecode: yes |
||||||
|
bytecode_security: Paranoid |
||||||
|
|
||||||
|
|
||||||
|
freshclam: |
||||||
|
log_syslog: yes |
||||||
|
log_facility: LOG_LOCAL0 |
||||||
|
pid_file: /run/clamav/freshclam.pid |
||||||
|
database_directory: "{{ clamav_db_dir }}" |
||||||
|
database_owner: "{{ clamav_user }}" |
||||||
|
update_log_file: /dev/stdout |
||||||
|
checks: 4 |
||||||
|
test_databases: no |
||||||
|
bytecode: yes |
||||||
|
safe_browsing: yes |
||||||
|
notify_clamd: "{{ clamav_conf_file }}" |
||||||
|
scripted_updates: no |
||||||
|
private_mirror: https://packages.microsoft.com/clamav |
||||||
|
|
||||||
|
|
||||||
|
milter: |
||||||
|
log_syslog: yes |
||||||
|
log_facility: LOG_LOCAL0 |
||||||
|
log_infected: Basic |
||||||
|
log_clean: Basic |
||||||
|
milter_socket: "inet:{{ mail_server.clamav_port | d(7357) }}" |
||||||
|
user: "{{ clamav_user }}" |
||||||
|
clamd_socket: "unix:{{ clamav_socket }}" |
||||||
|
max_file_size: "{{ clamav_max_file_size }}" |
||||||
|
on_infected: Reject |
||||||
|
add_header: Add |
||||||
|
report_hostname: "{{ (mail_server.mta_actual_hostname ~ '.' ~ mail_server.tld) if |
||||||
|
(mail_server.mta_actual_hostname is defined) and (mail_server.tld is defined) else 'clamav' }}" |
||||||
|
support_multiple_recipients: yes |
||||||
|
foreground: yes |
@ -0,0 +1,16 @@ |
|||||||
|
- name: restart clamd |
||||||
|
service: |
||||||
|
name: clamd |
||||||
|
state: restarted |
||||||
|
|
||||||
|
|
||||||
|
- name: restart freshclam |
||||||
|
service: |
||||||
|
name: freshclam |
||||||
|
state: restarted |
||||||
|
|
||||||
|
|
||||||
|
- name: restart clamav milter |
||||||
|
service: |
||||||
|
name: clamav-milter |
||||||
|
state: restarted |
@ -0,0 +1,97 @@ |
|||||||
|
- name: set clamav_cfg |
||||||
|
set_fact: |
||||||
|
clamav_cfg: "{{ clamav_default_config | d({}) | combine(clamav_config | d({}), recursive=true) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: install dependencies |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- clamav-daemon |
||||||
|
- alpine: clamav-daemon-openrc |
||||||
|
- clamav-milter |
||||||
|
|
||||||
|
|
||||||
|
- name: create user and group |
||||||
|
include_tasks: tasks/create_user.yml |
||||||
|
vars: |
||||||
|
user: |
||||||
|
name: "{{ clamav_user }}" |
||||||
|
group: "{{ clamav_group }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: create directories |
||||||
|
file: |
||||||
|
path: "{{ item }}" |
||||||
|
state: directory |
||||||
|
mode: 0700 |
||||||
|
owner: "{{ clamav_user }}" |
||||||
|
group: "{{ clamav_group }}" |
||||||
|
loop: |
||||||
|
- "{{ clamav_conf_dir }}" |
||||||
|
- "{{ clamav_db_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: template clamav configs |
||||||
|
template: |
||||||
|
src: config.j2 |
||||||
|
dest: "{{ item.dest }}" |
||||||
|
force: yes |
||||||
|
mode: 0400 |
||||||
|
owner: "{{ clamav_user }}" |
||||||
|
group: "{{ clamav_group }}" |
||||||
|
lstrip_blocks: yes |
||||||
|
notify: "{{ item.notify }}" |
||||||
|
loop: |
||||||
|
- { dest: "{{ clamav_conf_file }}", section: "clamav", notify: "restart clamd" } |
||||||
|
- { dest: "{{ clamav_freshclam_conf_file }}", section: "freshclam", notify: "restart freshclam" } |
||||||
|
- { dest: "{{ clamav_milter_conf_file }}", section: "milter", notify: "restart clamav milter" } |
||||||
|
|
||||||
|
|
||||||
|
- name: edit init script for clamd |
||||||
|
lineinfile: |
||||||
|
path: /etc/init.d/clamd |
||||||
|
regexp: '^CONF=' |
||||||
|
line: 'CONF={{ clamav_conf_file | quote }}' |
||||||
|
notify: restart clamd |
||||||
|
|
||||||
|
|
||||||
|
- name: edit init script for freshclam |
||||||
|
lineinfile: |
||||||
|
path: /etc/init.d/freshclam |
||||||
|
regexp: '^CONF=' |
||||||
|
line: 'CONF={{ clamav_freshclam_conf_file | quote }}' |
||||||
|
notify: restart freshclam |
||||||
|
|
||||||
|
|
||||||
|
- name: template init script for clamav milter |
||||||
|
template: |
||||||
|
src: milter_init.j2 |
||||||
|
dest: /etc/init.d/clamav-milter |
||||||
|
force: yes |
||||||
|
mode: "+x" |
||||||
|
notify: restart clamav milter |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ clamav_conf_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: enable and start services |
||||||
|
service: |
||||||
|
name: "{{ item }}" |
||||||
|
enabled: yes |
||||||
|
state: started |
||||||
|
loop: |
||||||
|
- clamd |
||||||
|
- freshclam |
||||||
|
- clamav-milter |
@ -0,0 +1,16 @@ |
|||||||
|
{% macro clamav_option(option) -%} |
||||||
|
{% set key = option.key.split('_') | map('capitalize') | join('') -%} |
||||||
|
|
||||||
|
{% if option.value is boolean -%} |
||||||
|
{{ key }} {{ 'yes' if option.value else 'no' }} |
||||||
|
{% elif option.value != None -%} |
||||||
|
{{ key }} {{ option.value }} |
||||||
|
{% endif -%} |
||||||
|
{% endmacro -%} |
||||||
|
|
||||||
|
|
||||||
|
{% if clamav_cfg[item.section] is mapping -%} |
||||||
|
{% for option in (clamav_cfg[item.section] | d({}) | dict2items) -%} |
||||||
|
{{ clamav_option(option) }} |
||||||
|
{%- endfor %} |
||||||
|
{% endif -%} |
@ -0,0 +1,14 @@ |
|||||||
|
#!/sbin/openrc-run |
||||||
|
|
||||||
|
name="$SVCNAME" |
||||||
|
directory="{{ clamav_db_dir }}" |
||||||
|
command="/usr/sbin/clamav-milter" |
||||||
|
command_user="{{ clamav_user ~ ':' ~ clamav_group }}" |
||||||
|
pidfile="/var/run/$SVCNAME.pid" |
||||||
|
supervisor="supervise-daemon" |
||||||
|
|
||||||
|
depend() { |
||||||
|
need net |
||||||
|
use dns |
||||||
|
after clamd |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
dropbear_dir: /etc/dropbear |
@ -0,0 +1,19 @@ |
|||||||
|
#!/sbin/openrc-run |
||||||
|
|
||||||
|
depend() { |
||||||
|
use logger dns |
||||||
|
need net |
||||||
|
after firewall |
||||||
|
} |
||||||
|
|
||||||
|
start() { |
||||||
|
ebegin "Starting dropbear" |
||||||
|
/usr/sbin/dropbear ${DROPBEAR_OPTS} |
||||||
|
eend $? |
||||||
|
} |
||||||
|
|
||||||
|
stop() { |
||||||
|
ebegin "Stopping dropbear" |
||||||
|
start-stop-daemon --stop --pidfile /var/run/dropbear.pid |
||||||
|
eend $? |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
- name: restart syslog |
||||||
|
service: |
||||||
|
name: syslog |
||||||
|
state: restarted |
||||||
|
|
||||||
|
|
||||||
|
- name: restart crond |
||||||
|
service: |
||||||
|
name: cron |
||||||
|
state: restarted |
||||||
|
|
||||||
|
|
||||||
|
- name: restart dropbear |
||||||
|
service: |
||||||
|
name: dropbear |
||||||
|
state: restarted |
@ -0,0 +1,94 @@ |
|||||||
|
- name: setup timezone |
||||||
|
shell: |
||||||
|
cmd: PATH=$PATH:/sbin; /sbin/setup-timezone -z {{ timezone | quote }} |
||||||
|
chdir: /sbin |
||||||
|
creates: "{{ ('/etc/zoneinfo', timezone) | path_join }}" |
||||||
|
notify: restart syslog |
||||||
|
when: timezone is string |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: upgrade alpine version |
||||||
|
replace: |
||||||
|
path: /etc/apk/repositories |
||||||
|
regexp: '/alpine/v\d+\.\d+/' |
||||||
|
replace: '/alpine/v{{ alpine_version }}/' |
||||||
|
|
||||||
|
|
||||||
|
- name: change apk repository |
||||||
|
replace: |
||||||
|
path: /etc/apk/repositories |
||||||
|
regexp: '^https:\/\/dl-cdn\.alpinelinux\.org\/alpine\/' |
||||||
|
replace: 'https://mirror.yandex.ru/mirrors/alpine/' |
||||||
|
when: use_alternative_apk_repo | d(false) == true |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: update repository index |
||||||
|
community.general.apk: |
||||||
|
update_cache: yes |
||||||
|
changed_when: no |
||||||
|
register: apk_result |
||||||
|
|
||||||
|
rescue: |
||||||
|
- name: fix repository keys |
||||||
|
command: |
||||||
|
cmd: /sbin/apk fix --allow-untrusted alpine-keys |
||||||
|
when: "'UNTRUSTED signature' in apk_result.stderr" |
||||||
|
|
||||||
|
- name: update repository index in untrusted mode |
||||||
|
command: |
||||||
|
cmd: /sbin/apk --allow-untrusted update |
||||||
|
when: "'UNTRUSTED signature' in apk_result.stderr" |
||||||
|
|
||||||
|
- name: upgrade basic dependencies in untrusted mode |
||||||
|
command: |
||||||
|
cmd: /sbin/apk --allow-untrusted upgrade apk-tools alpine-keys |
||||||
|
when: "'UNTRUSTED signature' in apk_result.stderr" |
||||||
|
|
||||||
|
- name: update repository index |
||||||
|
community.general.apk: |
||||||
|
update_cache: yes |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
|
||||||
|
- name: check if there are any updates |
||||||
|
command: |
||||||
|
cmd: /sbin/apk list -u |
||||||
|
register: updates_found |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
|
||||||
|
- name: pause and confirm updates |
||||||
|
pause: |
||||||
|
prompt: "{{ updates_found.stdout }}" |
||||||
|
when: (updates_found.stdout | length > 0) and (interactive | d(true) == true) and (hosts_strategy | d('') != 'free') |
||||||
|
changed_when: updates_found.stdout | length > 0 |
||||||
|
|
||||||
|
|
||||||
|
- name: upgrade all packages if updates are found |
||||||
|
community.general.apk: |
||||||
|
upgrade: yes |
||||||
|
when: updates_found.stdout | length > 0 |
||||||
|
|
||||||
|
|
||||||
|
- name: collect apk-new files |
||||||
|
find: |
||||||
|
paths: |
||||||
|
- /etc |
||||||
|
- /usr |
||||||
|
- /var |
||||||
|
patterns: "*.apk-new" |
||||||
|
recurse: yes |
||||||
|
depth: 8 |
||||||
|
register: new_files |
||||||
|
|
||||||
|
|
||||||
|
- name: remove apk-new files |
||||||
|
file: |
||||||
|
path: "{{ item.path }}" |
||||||
|
state: absent |
||||||
|
loop: "{{ new_files.files | flatten(levels=1) }}" |
@ -0,0 +1,52 @@ |
|||||||
|
- name: set timezone |
||||||
|
community.general.timezone: |
||||||
|
name: "{{ timezone }}" |
||||||
|
notify: restart crond |
||||||
|
when: timezone is defined |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: update repository index |
||||||
|
apt: |
||||||
|
force_apt_get: yes |
||||||
|
update_cache: yes |
||||||
|
changed_when: false |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure apt-show-versions is installed |
||||||
|
apt: |
||||||
|
force_apt_get: yes |
||||||
|
name: apt-show-versions |
||||||
|
state: latest |
||||||
|
|
||||||
|
|
||||||
|
- name: get upgradeable packages |
||||||
|
shell: |
||||||
|
cmd: apt-show-versions --upgradeable |
||||||
|
register: upgradeable |
||||||
|
changed_when: false |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: pause and confirm updates |
||||||
|
pause: |
||||||
|
prompt: "{{ upgradeable.stdout }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: upgrade all packages |
||||||
|
apt: |
||||||
|
force_apt_get: yes |
||||||
|
install_recommends: no |
||||||
|
upgrade: dist |
||||||
|
|
||||||
|
when: "(upgradeable.stdout_lines is defined) and (upgradeable.stdout_lines | length > 0)" |
||||||
|
|
||||||
|
|
||||||
|
- name: clean repository cache |
||||||
|
apt: |
||||||
|
force_apt_get: yes |
||||||
|
autoclean: yes |
||||||
|
autoremove: yes |
@ -0,0 +1,147 @@ |
|||||||
|
- block: |
||||||
|
- name: try to connect |
||||||
|
wait_for_connection: |
||||||
|
timeout: 10 |
||||||
|
|
||||||
|
- set_fact: |
||||||
|
ssh_ok: yes |
||||||
|
|
||||||
|
rescue: |
||||||
|
- name: save old ansible ssh args |
||||||
|
set_fact: |
||||||
|
old_ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | d('') }}" |
||||||
|
|
||||||
|
- name: disable key checking and enable password login |
||||||
|
set_fact: |
||||||
|
ssh_ok: no |
||||||
|
host_key_checking: no |
||||||
|
ansible_password: "{{ container_password | d(host_password) }}" |
||||||
|
ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | d('') }} -o StrictHostKeyChecking=no" |
||||||
|
|
||||||
|
- name: try to connect without key checking |
||||||
|
wait_for_connection: |
||||||
|
timeout: 10 |
||||||
|
|
||||||
|
|
||||||
|
- name: gather facts |
||||||
|
setup: |
||||||
|
gather_subset: |
||||||
|
- min |
||||||
|
- distribution |
||||||
|
|
||||||
|
|
||||||
|
- name: generate host ssh key |
||||||
|
include_tasks: gen_ssh_key.yml |
||||||
|
when: (use_ssh_keys | d(true) == true) and ('containers' not in group_names) |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: remove default dropbear keys |
||||||
|
file: |
||||||
|
path: "{{ (dropbear_dir, item) | path_join }}" |
||||||
|
state: absent |
||||||
|
loop: |
||||||
|
- dropbear_dss_host_key |
||||||
|
- dropbear_rsa_host_key |
||||||
|
- dropbear_ecdsa_host_key |
||||||
|
notify: restart dropbear |
||||||
|
|
||||||
|
|
||||||
|
- name: generate ed25519 dropbear key if missing |
||||||
|
command: |
||||||
|
cmd: "dropbearkey -t ed25519 -f {{ (dropbear_dir, 'dropbear_ed25519_host_key') | path_join | quote }}" |
||||||
|
creates: "{{ (dropbear_dir, 'dropbear_ed25519_host_key') | path_join }}" |
||||||
|
notify: restart dropbear |
||||||
|
|
||||||
|
|
||||||
|
- name: get remote host public key |
||||||
|
command: |
||||||
|
cmd: "dropbearkey -y -f {{ (dropbear_dir, 'dropbear_ed25519_host_key') | path_join | quote }}" |
||||||
|
register: pubkey |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
|
||||||
|
- name: get actual public key |
||||||
|
set_fact: |
||||||
|
host_ssh_pubkey: "{{ pubkey.stdout_lines | map('regex_search', '^ssh-ed25519.*$') | select('string') | first }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: fail if public key is missing |
||||||
|
fail: |
||||||
|
msg: "remote host ssh public key is missing" |
||||||
|
when: host_ssh_pubkey | length == 0 |
||||||
|
|
||||||
|
|
||||||
|
- name: add public key to known_hosts on ansible controller |
||||||
|
known_hosts: |
||||||
|
key: "{{ ansible_host }} {{ host_ssh_pubkey }}" |
||||||
|
name: "{{ ansible_host }}" |
||||||
|
delegate_to: localhost |
||||||
|
|
||||||
|
|
||||||
|
- name: edit dropbear conf file |
||||||
|
lineinfile: |
||||||
|
path: /etc/conf.d/dropbear |
||||||
|
regexp: '^DROPBEAR_OPTS=.*$' |
||||||
|
line: "DROPBEAR_OPTS=\"-r {{ (dropbear_dir, 'dropbear_ed25519_host_key') | path_join | quote }} -jk -T 5 -K 360 -I 7200\"" |
||||||
|
notify: restart dropbear |
||||||
|
|
||||||
|
|
||||||
|
- name: copy dropbear init file |
||||||
|
copy: |
||||||
|
src: dropbear_init |
||||||
|
dest: /etc/init.d/dropbear |
||||||
|
force: yes |
||||||
|
notify: restart dropbear |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure remote host has ansible key in authorized_keys file |
||||||
|
lineinfile: |
||||||
|
path: /root/.ssh/authorized_keys |
||||||
|
line: "{{ container_key.public_key }}" |
||||||
|
create: yes |
||||||
|
mode: 0400 |
||||||
|
when: container_key is defined and container_key.public_key is defined |
||||||
|
|
||||||
|
when: ansible_distribution == 'Alpine' |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: if key checking was disabled |
||||||
|
block: |
||||||
|
- name: set it back on |
||||||
|
set_fact: |
||||||
|
host_key_checking: yes |
||||||
|
ansible_ssh_extra_args: "{{ old_ansible_ssh_extra_args }}" |
||||||
|
ansible_password: "{{ None }}" |
||||||
|
|
||||||
|
- name: try to connect |
||||||
|
wait_for_connection: |
||||||
|
timeout: 10 |
||||||
|
|
||||||
|
- set_fact: |
||||||
|
ssh_ok: true |
||||||
|
|
||||||
|
when: not ssh_ok |
||||||
|
|
||||||
|
|
||||||
|
- name: add etc directory to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- /etc |
||||||
|
|
||||||
|
|
||||||
|
- name: alpine setup |
||||||
|
include_tasks: alpine.yml |
||||||
|
when: ansible_distribution == 'Alpine' |
||||||
|
|
||||||
|
|
||||||
|
- name: debian setup |
||||||
|
include_tasks: debian.yml |
||||||
|
when: ansible_distribution == 'Debian' |
@ -0,0 +1,7 @@ |
|||||||
|
container_description: Managed by Ansible |
||||||
|
container_pool: production |
||||||
|
|
||||||
|
container_distro: alpine |
||||||
|
container_template: |
||||||
|
alpine: alpine-3.15-default_20211202_amd64.tar.xz |
||||||
|
debian: debian-11-standard_11.3-1_amd64.tar.zst |
@ -0,0 +1,186 @@ |
|||||||
|
- name: specify connection parameters |
||||||
|
set_fact: |
||||||
|
pm_api_host: "{{ hostvars[selected_node]['ansible_host'] | mandatory }}" |
||||||
|
pm_api_user: "{{ hostvars[selected_node]['api_user'] | d('root@pam') }}" |
||||||
|
pm_api_password: "{{ hostvars[selected_node]['api_password'] | d(hostvars[selected_node]['ansible_password']) }}" |
||||||
|
pm_lxc_storage: "{{ hostvars[selected_node]['lxc_storage'] | d('local-zfs') }}" |
||||||
|
no_log: yes |
||||||
|
|
||||||
|
|
||||||
|
- name: validate template and distribution parameters |
||||||
|
fail: |
||||||
|
msg: some container parameters are missing or invalid |
||||||
|
when: (container_distro is not defined) or (container_template is not mapping) or |
||||||
|
(container_template[container_distro] is not defined) or |
||||||
|
(container_id is not defined) or (container_password is not defined) |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure pool exists on cluster node |
||||||
|
command: |
||||||
|
cmd: "pveum pool add {{ container_pool | quote }}" |
||||||
|
register: pool_res |
||||||
|
changed_when: pool_res.rc == 0 |
||||||
|
failed_when: (pool_res.rc != 0) and not ((pool_res.rc == 255) and ('already exists' in pool_res.stderr)) |
||||||
|
when: container_pool is defined |
||||||
|
delegate_to: "{{ selected_node }}" |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: ensure pip3 is installed on local node |
||||||
|
package: |
||||||
|
name: py3-pip |
||||||
|
run_once: yes |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure proxmoxer is installed on local node |
||||||
|
pip: |
||||||
|
name: proxmoxer |
||||||
|
run_once: yes |
||||||
|
|
||||||
|
|
||||||
|
- name: generate host ssh key |
||||||
|
include_tasks: gen_ssh_key.yml |
||||||
|
when: use_ssh_keys | d(true) == true |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure there is a container template |
||||||
|
community.general.proxmox_template: |
||||||
|
node: "{{ selected_node }}" |
||||||
|
api_host: "{{ pm_api_host }}" |
||||||
|
api_user: "{{ pm_api_user }}" |
||||||
|
api_password: "{{ pm_api_password }}" |
||||||
|
content_type: vztmpl |
||||||
|
template: "{{ container_template[container_distro] }}" |
||||||
|
validate_certs: no |
||||||
|
timeout: 20 |
||||||
|
|
||||||
|
|
||||||
|
- name: create container if not exists |
||||||
|
community.general.proxmox: |
||||||
|
node: "{{ selected_node }}" |
||||||
|
api_host: "{{ pm_api_host }}" |
||||||
|
api_user: "{{ pm_api_user }}" |
||||||
|
api_password: "{{ pm_api_password }}" |
||||||
|
|
||||||
|
cores: "{{ hardware.cores }}" |
||||||
|
cpus: "{{ hardware.cpus }}" |
||||||
|
cpuunits: "{{ hardware.cpuunits }}" |
||||||
|
disk: "{{ hardware.disk | string }}" |
||||||
|
memory: "{{ hardware.memory }}" |
||||||
|
swap: "{{ hardware.swap }}" |
||||||
|
|
||||||
|
description: "{{ container_description | d(omit) }}" |
||||||
|
hostname: "{{ inventory_hostname }}" |
||||||
|
pool: "{{ container_pool | d(omit) }}" |
||||||
|
vmid: "{{ container_id }}" |
||||||
|
|
||||||
|
password: "{{ container_password }}" |
||||||
|
pubkey: "{{ (container_key | d({})).public_key | d(omit) }}" |
||||||
|
|
||||||
|
ostemplate: "local:vztmpl/{{ container_template[container_distro] }}" |
||||||
|
netif: "{\"net0\":\ |
||||||
|
\"name=eth0,hwaddr={{ container_mac | d(mac_prefix | community.general.random_mac(seed=inventory_hostname)) }},\ |
||||||
|
ip={{ ansible_host }}/{{ networks[container_network].gw | ansible.utils.ipaddr('prefix') }},\ |
||||||
|
gw={{ networks[container_network].gw | ansible.utils.ipaddr('address') }},\ |
||||||
|
bridge=vmbr0,\ |
||||||
|
firewall=0,\ |
||||||
|
tag={{ networks[container_network].tag }},\ |
||||||
|
type=veth,\ |
||||||
|
mtu={{ container_mtu | d(hostvars[selected_node]['container_mtu'] | d(1500)) }}\"}" |
||||||
|
nameserver: "{%- if container_nameserver is defined -%}\ |
||||||
|
{{ hostvars[container_nameserver]['ansible_host'] }}\ |
||||||
|
{%- elif services.filtering_ns is defined -%}\ |
||||||
|
{%- if services.filtering_ns | type_debug == 'list' -%} |
||||||
|
{{ hostvars[services.filtering_ns[0].hostname]['ansible_host'] }}\ |
||||||
|
{%- else -%} |
||||||
|
{{ hostvars[services.filtering_ns.hostname]['ansible_host'] }}\ |
||||||
|
{%- endif -%} |
||||||
|
{%- elif container_default_nameserver is defined -%}\ |
||||||
|
{{ container_default_nameserver }}\ |
||||||
|
{%- else -%}\ |
||||||
|
{{ omit }}\ |
||||||
|
{%- endif -%}" |
||||||
|
onboot: yes |
||||||
|
proxmox_default_behavior: no_defaults |
||||||
|
storage: "{{ pm_lxc_storage }}" |
||||||
|
unprivileged: yes |
||||||
|
timeout: 240 |
||||||
|
|
||||||
|
mounts: >- |
||||||
|
{ {%- for item in (container_mounts | d([])) -%} |
||||||
|
"{{ item.id }}":"{{ pm_lxc_storage }}:{{ item.size | mandatory }},mp={{ item.mp | mandatory }}{% if item.readonly is defined and item.readonly %},ro=1{% endif %}", |
||||||
|
{%- endfor -%} } |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: add features to lxc config |
||||||
|
lineinfile: |
||||||
|
path: "/etc/pve/lxc/{{ container_id }}.conf" |
||||||
|
line: "features: {{ container_features | join(',') }}" |
||||||
|
when: container_features | d([]) | length > 0 |
||||||
|
|
||||||
|
|
||||||
|
- name: check that lxc config is correct |
||||||
|
lineinfile: |
||||||
|
path: "/etc/pve/lxc/{{ container_id }}.conf" |
||||||
|
regexp: "^{{ item.name }}:(\\s*).*$" |
||||||
|
line: "{{ item.name | mandatory }}:\\g<1>{{ item.value | mandatory }}" |
||||||
|
backrefs: yes |
||||||
|
loop: |
||||||
|
- { name: cpus, value: "{{ hardware.cpus }}" } |
||||||
|
- { name: cores, value: "{{ [hardware.cores, hostvars[selected_node]['max_cores'] | d(hardware.cores)] | min }}" } |
||||||
|
- { name: cpuunits, value: "{{ hardware.cpuunits }}" } |
||||||
|
- { name: memory, value: "{{ hardware.memory }}" } |
||||||
|
- { name: swap, value: "{{ hardware.swap }}" } |
||||||
|
- { name: onboot, value: "{{ '1' if (container_active | d(true) == true) else '0' }}" } |
||||||
|
|
||||||
|
|
||||||
|
- name: set startup order and delay |
||||||
|
lineinfile: |
||||||
|
path: "/etc/pve/lxc/{{ container_id }}.conf" |
||||||
|
regexp: '^startup:.*$' |
||||||
|
line: "startup: {{ 'order=' ~ (container_order | d(role_dependency[host_primary_role] | d('0'))) ~ ((',up=' ~ container_startup_delay) if container_startup_delay is defined else '') }}" |
||||||
|
insertbefore: '^[^\#]' |
||||||
|
firstmatch: yes |
||||||
|
when: (container_order is defined) or (role_dependency[host_primary_role] is defined) or (container_startup_delay is defined) |
||||||
|
|
||||||
|
|
||||||
|
- name: ensure that cpulimit is not set |
||||||
|
lineinfile: |
||||||
|
path: "/etc/pve/lxc/{{ container_id }}.conf" |
||||||
|
regexp: '^cpulimit:.*$' |
||||||
|
state: absent |
||||||
|
|
||||||
|
delegate_to: "{{ selected_node }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: start/stop container |
||||||
|
community.general.proxmox: |
||||||
|
node: "{{ selected_node }}" |
||||||
|
api_host: "{{ pm_api_host }}" |
||||||
|
api_user: "{{ pm_api_user }}" |
||||||
|
api_password: "{{ pm_api_password }}" |
||||||
|
vmid: "{{ container_id }}" |
||||||
|
proxmox_default_behavior: no_defaults |
||||||
|
state: "{{ 'started' if (container_active | d(true) == true) else 'stopped' }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: end playbook for current host if container is set to inactive |
||||||
|
meta: end_host |
||||||
|
when: container_active | d(true) == false |
||||||
|
|
||||||
|
|
||||||
|
- name: wait until networking is avaliable |
||||||
|
command: |
||||||
|
cmd: "ping -c1 -W1 {{ ansible_host | quote }}" |
||||||
|
register: ping_result |
||||||
|
until: ping_result.rc == 0 |
||||||
|
retries: 5 |
||||||
|
delay: 2 |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
delegate_to: 127.0.0.1 |
||||||
|
|
||||||
|
|
||||||
|
- name: preconfigure container |
||||||
|
include_tasks: preconf.yml |
@ -0,0 +1,66 @@ |
|||||||
|
- block: |
||||||
|
- name: install basic dependencies |
||||||
|
include_tasks: tasks/pct_command.yml |
||||||
|
vars: |
||||||
|
pct_command: "{{ item.pct_command }}" |
||||||
|
chg_substr: "{{ item.chg_substr | d(omit) }}" |
||||||
|
loop: |
||||||
|
- pct_command: apk update |
||||||
|
- pct_command: apk add python3 |
||||||
|
chg_substr: Installing |
||||||
|
- pct_command: apk add dropbear |
||||||
|
chg_substr: Installing |
||||||
|
- pct_command: rc-update add dropbear |
||||||
|
chg_substr: added to runlevel |
||||||
|
|
||||||
|
- name: install dropbear-scp if this is not an ansible controller |
||||||
|
include_tasks: tasks/pct_command.yml |
||||||
|
vars: |
||||||
|
pct_command: apk add dropbear-scp |
||||||
|
chg_substr: Installing |
||||||
|
when: (inventory_hostname != 'ansible') and ((primary_role is not defined) or (primary_role != 'ansible')) |
||||||
|
and alpine_version is version('3.15', '<=') |
||||||
|
|
||||||
|
- name: install openssh-sftp-server due to openssh 9 scp deprecation |
||||||
|
include_tasks: tasks/pct_command.yml |
||||||
|
vars: |
||||||
|
pct_command: apk add openssh-sftp-server |
||||||
|
chg_substr: Installing |
||||||
|
when: alpine_version is version('3.16', '>=') |
||||||
|
|
||||||
|
- name: start dropbear |
||||||
|
include_tasks: tasks/pct_command.yml |
||||||
|
vars: |
||||||
|
pct_command: service dropbear start |
||||||
|
chg_substr: \* Starting dropbear ... [ ok ] |
||||||
|
|
||||||
|
when: (container_distro | lower) == 'alpine' |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: install basic dependencies |
||||||
|
include_tasks: tasks/pct_command.yml |
||||||
|
vars: |
||||||
|
pct_command: "{{ item.pct_command }}" |
||||||
|
chg_substr: "{{ item.chg_substr | default(omit) }}" |
||||||
|
loop: |
||||||
|
- pct_command: apt-get --assume-yes update |
||||||
|
- pct_command: apt-get --assume-yes install python3 |
||||||
|
chg_substr: The following NEW packages |
||||||
|
- pct_command: apt-get --assume-yes install openssh-server |
||||||
|
chg_substr: The following NEW packages |
||||||
|
- pct_command: systemctl enable ssh.service |
||||||
|
chg_substr: Synchronizing state |
||||||
|
|
||||||
|
- name: edit sshd config |
||||||
|
include_tasks: tasks/pct_command.yml |
||||||
|
vars: |
||||||
|
pct_command: "sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/g' /etc/ssh/sshd_config" |
||||||
|
|
||||||
|
|
||||||
|
- name: start sshd |
||||||
|
include_tasks: tasks/pct_command.yml |
||||||
|
vars: |
||||||
|
pct_command: systemctl start ssh.service |
||||||
|
|
||||||
|
when: (container_distro | lower) in ['debian', 'ubuntu'] |
@ -0,0 +1,9 @@ |
|||||||
|
coredns_user: coredns |
||||||
|
coredns_group: coredns |
||||||
|
coredns_conf_dir: /etc/coredns |
||||||
|
|
||||||
|
coredns_conf_file: "{{ coredns_conf_dir }}/coredns.conf" |
||||||
|
coredns_tls_file: "{{ coredns_conf_dir }}/tls.conf" |
||||||
|
|
||||||
|
coredns_cert_file: "{{ coredns_conf_dir }}/ecc384.crt" |
||||||
|
coredns_key_file: "{{ coredns_conf_dir }}/ecc384.key" |
@ -0,0 +1,4 @@ |
|||||||
|
- name: restart coredns |
||||||
|
service: |
||||||
|
name: coredns |
||||||
|
state: restarted |
@ -0,0 +1,119 @@ |
|||||||
|
- name: check if record is an object |
||||||
|
fail: |
||||||
|
msg: record must be an object |
||||||
|
when: record is not mapping |
||||||
|
|
||||||
|
|
||||||
|
- name: check if record zone is a string |
||||||
|
fail: |
||||||
|
msg: record zone must be a string |
||||||
|
when: record.zone is defined and record.zone is not string |
||||||
|
|
||||||
|
|
||||||
|
- name: check if record zone exists |
||||||
|
fail: |
||||||
|
msg: '"{{ record.zone }}" does not seem to be a valid zone' |
||||||
|
when: (record.zone is defined) and |
||||||
|
(record.zone != 'root') and |
||||||
|
((int_zones is not defined) or (record.zone not in int_zones)) |
||||||
|
|
||||||
|
|
||||||
|
- name: construct record parameters |
||||||
|
set_fact: |
||||||
|
ns_zone: "{%- if (record.zone is defined) and (record.zone != 'root') -%}{{ record.zone }}\ |
||||||
|
{%- else -%}{{ ns_tld | d(int_tld) }}\ |
||||||
|
{%- endif -%}" |
||||||
|
ns_name: "{%- if record.name is defined -%}{{ record.name }}\ |
||||||
|
{%- else -%}{{ inventory_hostname }}\ |
||||||
|
{%- endif -%}" |
||||||
|
ns_type: "{%- if record.type is defined -%}{{ record.type | upper }}\ |
||||||
|
{%- else -%}A\ |
||||||
|
{%- endif -%}" |
||||||
|
ns_value: "{%- if record.value is defined -%}{{ record.value }}\ |
||||||
|
{%- else -%}{{ ansible_host }}\ |
||||||
|
{%- endif -%}" |
||||||
|
|
||||||
|
- name: set ns_quote |
||||||
|
set_fact: |
||||||
|
ns_quote: "{{ '\"' if ns_type == 'TXT' else '' }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: construct full name |
||||||
|
set_fact: |
||||||
|
ns_full_name: '{%- if ns_name != "@" -%}{{ ns_name }}.{%- endif -%}{{ ns_zone }}' |
||||||
|
|
||||||
|
|
||||||
|
- name: construct regex part |
||||||
|
set_fact: |
||||||
|
ns_regex_part: '{%- if record.allow_multiple is defined -%}{{ (ns_quote ~ ns_value ~ ns_quote) | regex_escape() }}\.?{%- else -%}{{ "" | string }}{%- endif -%}' |
||||||
|
|
||||||
|
|
||||||
|
- name: construct regex |
||||||
|
set_fact: |
||||||
|
ns_regex: '^{{ ns_full_name | regex_escape() }}\s+\d+\s+IN\s+{{ ns_type | regex_escape() }}\s+{{ ns_regex_part }}' |
||||||
|
|
||||||
|
|
||||||
|
- name: show debug info |
||||||
|
debug: |
||||||
|
msg: "{{ ns_zone }} {{ ns_name }} {{ ns_type }} {{ ns_quote ~ ns_value ~ ns_quote }} --> {{ ns_regex }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: slurp zone file |
||||||
|
slurp: |
||||||
|
src: "{{ coredns_conf_dir ~ '/' ~ (ns_tld | d(int_tld)) ~ '.zone' }}" |
||||||
|
register: zf |
||||||
|
changed_when: false |
||||||
|
|
||||||
|
|
||||||
|
- name: enumerate stdout lines to check if an entry already exists |
||||||
|
set_fact: |
||||||
|
ns_exists: "{{ (zf.content | b64decode).split('\n') | select('search', ns_regex) | list | length > 0 }}" |
||||||
|
|
||||||
|
|
||||||
|
- block: |
||||||
|
- name: fail if there are multiple records |
||||||
|
fail: |
||||||
|
msg: single record mode is selected, but multiple records found |
||||||
|
when: (zf.content | b64decode).split('\n') | select('search', ns_regex) | list | length > 1 |
||||||
|
|
||||||
|
|
||||||
|
- name: grab the value |
||||||
|
set_fact: |
||||||
|
ns_old_value: "{{ (zf.content | b64decode).split('\n') | select('search', ns_regex) | map('regex_search', '\\s+?(\\S+?)\\.?$', '\\1') | first | join('') }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: replace the record |
||||||
|
lineinfile: |
||||||
|
path: "{{ coredns_conf_dir ~ '/' ~ (ns_tld | d(int_tld)) ~ '.zone' }}" |
||||||
|
regexp: '^\s*{{ ns_name | regex_escape() }}\s+IN\s+{{ ns_type | regex_escape() }}\s+' |
||||||
|
line: "{{ ns_name }}\tIN\t{{ ns_type }}\t{{ ns_quote ~ ns_value ~ ns_quote }}" |
||||||
|
backrefs: yes |
||||||
|
when: ns_old_value != (ns_quote ~ ns_value ~ ns_quote) |
||||||
|
register: rr1 |
||||||
|
|
||||||
|
when: ns_exists and rrset.allow_multiple is not defined |
||||||
|
|
||||||
|
|
||||||
|
- name: add the record if it is missing |
||||||
|
lineinfile: |
||||||
|
path: "{{ coredns_conf_dir ~ '/' ~ (ns_tld | d(int_tld)) ~ '.zone' }}" |
||||||
|
line: "{{ ns_name }}\tIN\t{{ ns_type }}\t{{ ns_quote ~ ns_value ~ ns_quote }}" |
||||||
|
when: not ns_exists |
||||||
|
register: rr2 |
||||||
|
|
||||||
|
|
||||||
|
- name: determine if records were changed |
||||||
|
set_fact: |
||||||
|
ns_records_changed: "{{ ((rr1 is defined) and rr1.changed) or ((rr2 is defined) and rr2.changed) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: change serial |
||||||
|
include_tasks: increase_serial.yml |
||||||
|
when: ns_records_changed | d(false) == true |
||||||
|
|
||||||
|
|
||||||
|
- name: restart coredns |
||||||
|
service: |
||||||
|
name: coredns |
||||||
|
state: restarted |
||||||
|
when: (ns_instant | d(false) == true) and (ns_records_changed or ns_serial_changed) |
@ -0,0 +1,21 @@ |
|||||||
|
- name: add default record |
||||||
|
include_tasks: add_record.yml |
||||||
|
vars: |
||||||
|
record: {} |
||||||
|
when: (ns_records | d([]) | length) == 0 |
||||||
|
|
||||||
|
|
||||||
|
- name: process other items |
||||||
|
include_tasks: add_record.yml |
||||||
|
loop: "{{ ns_records | d([]) }}" |
||||||
|
loop_control: |
||||||
|
loop_var: record |
||||||
|
|
||||||
|
|
||||||
|
- name: restart coredns |
||||||
|
service: |
||||||
|
name: coredns |
||||||
|
state: restarted |
||||||
|
when: (ns_instant | d(false) == false) and |
||||||
|
((ns_records_changed | d(false) == true) or |
||||||
|
(ns_serial_changed | d(false) == true)) |
@ -0,0 +1,47 @@ |
|||||||
|
- name: slurp zone file |
||||||
|
slurp: |
||||||
|
src: "{{ coredns_conf_dir ~ '/' ~ (ns_tld | d(int_tld)) ~ '.zone' }}" |
||||||
|
register: zf |
||||||
|
changed_when: false |
||||||
|
|
||||||
|
|
||||||
|
- name: get SOA serial value |
||||||
|
set_fact: |
||||||
|
ns_old_serial: '{{ zf.content | b64decode | regex_search(''@\s+IN\s+SOA\s+\S+\s+\S+\s*\(\s*(\d+)'', ''\1'') | first }}' |
||||||
|
|
||||||
|
|
||||||
|
- name: get current date |
||||||
|
include_tasks: tasks/get_datetime.yml |
||||||
|
vars: |
||||||
|
format: YYMMDD |
||||||
|
|
||||||
|
|
||||||
|
- name: replace outdated serial with current date |
||||||
|
set_fact: |
||||||
|
ns_new_serial: "{{ (current_date_time | string) ~ '01'}}" |
||||||
|
when: ns_old_serial[:8] != (current_date_time | string) |
||||||
|
|
||||||
|
|
||||||
|
- name: increase current serial |
||||||
|
set_fact: |
||||||
|
ns_new_serial: "{{ (ns_old_serial | int) + 1 }}" |
||||||
|
when: (ns_old_serial[:8] == (current_date_time | string)) and ((ns_old_serial[8:10] | int) < 99) |
||||||
|
|
||||||
|
|
||||||
|
- name: do not change current serial if it had more than 99 iterations |
||||||
|
set_fact: |
||||||
|
ns_new_serial: "{{ ns_old_serial }}" |
||||||
|
when: (ns_old_serial[:8] == (current_date_time | string)) and ((ns_old_serial[8:10] | int) >= 99) |
||||||
|
|
||||||
|
|
||||||
|
- name: insert new serial |
||||||
|
replace: |
||||||
|
path: "{{ coredns_conf_dir ~ '/' ~ (ns_tld | d(int_tld)) ~ '.zone' }}" |
||||||
|
regexp: '(@\s+IN\s+SOA\s+\S+\s+\S+\s*\(\s*){{ ns_old_serial }}' |
||||||
|
replace: '\g<1>{{ ns_new_serial }}' |
||||||
|
register: result |
||||||
|
|
||||||
|
|
||||||
|
- name: set fact if serial was changed |
||||||
|
set_fact: |
||||||
|
ns_serial_changed: "{{ result.changed }}" |
@ -0,0 +1,93 @@ |
|||||||
|
- name: install coredns and dependencies |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- coredns |
||||||
|
- alpine: coredns-openrc |
||||||
|
|
||||||
|
|
||||||
|
- name: create user and group |
||||||
|
include_tasks: tasks/create_user.yml |
||||||
|
vars: |
||||||
|
user: |
||||||
|
name: "{{ coredns_user }}" |
||||||
|
group: "{{ coredns_group }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: create config directory |
||||||
|
file: |
||||||
|
path: "{{ coredns_conf_dir }}" |
||||||
|
state: directory |
||||||
|
owner: "{{ coredns_user }}" |
||||||
|
group: "{{ coredns_group }}" |
||||||
|
notify: restart coredns |
||||||
|
|
||||||
|
|
||||||
|
- name: template corefile |
||||||
|
template: |
||||||
|
src: corefile.j2 |
||||||
|
dest: "{{ coredns_conf_file }}" |
||||||
|
force: yes |
||||||
|
owner: "{{ coredns_user }}" |
||||||
|
group: "{{ coredns_group }}" |
||||||
|
mode: 0400 |
||||||
|
notify: restart coredns |
||||||
|
|
||||||
|
|
||||||
|
- name: template empty tls file if missing |
||||||
|
copy: |
||||||
|
content: '' |
||||||
|
dest: "{{ coredns_tls_file }}" |
||||||
|
force: no |
||||||
|
owner: "{{ coredns_user }}" |
||||||
|
group: "{{ coredns_group }}" |
||||||
|
mode: 0400 |
||||||
|
notify: restart coredns |
||||||
|
|
||||||
|
|
||||||
|
- name: template root zone if missing |
||||||
|
template: |
||||||
|
src: zone.j2 |
||||||
|
dest: "{{ coredns_conf_dir ~ '/' ~ (ns_tld | d(int_tld)) ~ '.zone' }}" |
||||||
|
force: no |
||||||
|
mode: 0400 |
||||||
|
owner: "{{ coredns_user }}" |
||||||
|
group: "{{ coredns_group }}" |
||||||
|
notify: restart coredns |
||||||
|
|
||||||
|
|
||||||
|
- name: edit service config |
||||||
|
lineinfile: |
||||||
|
path: /etc/conf.d/coredns |
||||||
|
regexp: "^COREDNS_CONFIG=" |
||||||
|
line: "COREDNS_CONFIG={{ coredns_conf_file | quote }}" |
||||||
|
notify: restart coredns |
||||||
|
|
||||||
|
|
||||||
|
- name: template init script |
||||||
|
template: |
||||||
|
src: init.j2 |
||||||
|
dest: /etc/init.d/coredns |
||||||
|
force: yes |
||||||
|
mode: 0755 |
||||||
|
notify: restart coredns |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ coredns_conf_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: enable and start coredns |
||||||
|
service: |
||||||
|
name: coredns |
||||||
|
enabled: yes |
||||||
|
state: started |
@ -0,0 +1,28 @@ |
|||||||
|
- name: deploy ecc384 cert |
||||||
|
include_role: |
||||||
|
name: ca |
||||||
|
vars: |
||||||
|
function: certs |
||||||
|
ca_options: |
||||||
|
mode: '0400' |
||||||
|
owner: "{{ coredns_user }}" |
||||||
|
group: "{{ coredns_group }}" |
||||||
|
concat_inter: true |
||||||
|
preset: web |
||||||
|
ocsp_must_staple: false |
||||||
|
notify: restart coredns |
||||||
|
ca_certs: |
||||||
|
- type: ecc384 |
||||||
|
key: "{{ coredns_key_file }}" |
||||||
|
cert: "{{ coredns_cert_file }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: template tls snippet file |
||||||
|
template: |
||||||
|
src: tls.j2 |
||||||
|
dest: "{{ coredns_tls_file }}" |
||||||
|
force: yes |
||||||
|
owner: "{{ coredns_user }}" |
||||||
|
group: "{{ coredns_group }}" |
||||||
|
mode: 0400 |
||||||
|
notify: restart coredns |
@ -0,0 +1,13 @@ |
|||||||
|
- name: install coredns |
||||||
|
include_tasks: install.yml |
||||||
|
when: function == 'install' |
||||||
|
|
||||||
|
|
||||||
|
- name: install coredns tls enhancements |
||||||
|
include_tasks: install_tls.yml |
||||||
|
when: function == 'install_tls' |
||||||
|
|
||||||
|
|
||||||
|
- name: add records |
||||||
|
include_tasks: add_records.yml |
||||||
|
when: function == 'add_records' |
@ -0,0 +1,15 @@ |
|||||||
|
(common) { |
||||||
|
root {{ (coredns_conf_dir ~ '/') | quote }} |
||||||
|
file {{ ((ns_tld | d(int_tld)) ~ '.zone') | quote }} |
||||||
|
|
||||||
|
any |
||||||
|
bufsize {{ ns_edns0_bufsize | d(1232) }} |
||||||
|
errors |
||||||
|
loadbalance |
||||||
|
} |
||||||
|
|
||||||
|
{{ ns_tld | d(int_tld) }} { |
||||||
|
import common |
||||||
|
} |
||||||
|
|
||||||
|
import {{ coredns_tls_file | quote }} |
@ -0,0 +1,14 @@ |
|||||||
|
#!/sbin/openrc-run |
||||||
|
|
||||||
|
name="$SVCNAME" |
||||||
|
directory="{{ coredns_conf_dir }}" |
||||||
|
command="/usr/bin/coredns" |
||||||
|
command_args="-conf ${COREDNS_CONFIG} ${COREDNS_EXTRA_ARGS}" |
||||||
|
command_user="{{ coredns_user }}:{{ coredns_group }}" |
||||||
|
pidfile="/var/run/$SVCNAME.pid" |
||||||
|
command_background=true |
||||||
|
start_stop_daemon_args="--stdout-logger logger --stderr-logger logger" |
||||||
|
|
||||||
|
depend() { |
||||||
|
after net |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
tls://{{ ns_tld | d(int_tld) }}:853 { |
||||||
|
import common |
||||||
|
tls {{ coredns_cert_file | quote }} {{ coredns_key_file | quote }} |
||||||
|
} |
||||||
|
|
||||||
|
https://{{ ns_tld | d(int_tld) }} { |
||||||
|
import common |
||||||
|
tls {{ coredns_cert_file | quote }} {{ coredns_key_file | quote }} |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
{%- set primary_ns = inventory_hostname -%} |
||||||
|
|
||||||
|
{%- if ns_server_group is defined -%} |
||||||
|
{%- set primary_ns = hostvars[groups[ns_server_group][0]]['inventory_hostname'] -%} |
||||||
|
{%- endif -%} |
||||||
|
|
||||||
|
{%- set this_name = (ns_name | d(inventory_hostname)) -%} |
||||||
|
{%- set this_primary_name = (hostvars[primary_ns]['ns_name'] | d(hostvars[primary_ns]['inventory_hostname'])) -%} |
||||||
|
{%- set this_tld = (hostvars[primary_ns]['ns_tld'] | d(ns_tld) | d(int_tld)) -%} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$ORIGIN {{ this_tld }}. |
||||||
|
$TTL {{ ns_ttl | d(300) }} |
||||||
|
|
||||||
|
@ IN SOA {{ this_name ~ '.' ~ this_tld }}. {{ (ns_admin | replace('@', '.')) if ns_admin is defined else ('admin' ~ '.' ~ this_tld) }}. ( |
||||||
|
2021010101 |
||||||
|
{{ ns_refresh | d(1200) }} |
||||||
|
{{ ns_retry | d(300) }} |
||||||
|
{{ ns_expire | d(1209600) }} |
||||||
|
{{ ns_neg_ttl | d(300) }} |
||||||
|
) |
||||||
|
|
||||||
|
{% if ns_server_group is defined -%} |
||||||
|
{% for host in groups[ns_server_group] -%} |
||||||
|
@ IN NS {{ (hostvars[host]['ns_name'] | d(hostvars[host]['inventory_hostname'])) ~ '.' ~ this_tld }}. |
||||||
|
{{ hostvars[host]['ns_name'] | d(hostvars[host]['inventory_hostname']) }} IN A {{ hostvars[host]['ansible_host'] }} |
||||||
|
{% endfor -%} |
||||||
|
{% else -%} |
||||||
|
@ IN NS {{ this_primary_name ~ '.' ~ this_tld }}. |
||||||
|
{{ this_primary_name }} IN A {{ ansible_host }} |
||||||
|
{% endif -%} |
@ -0,0 +1 @@ |
|||||||
|
crl_dir: /opt/crl |
@ -0,0 +1,41 @@ |
|||||||
|
- name: install and configure nginx |
||||||
|
include_role: |
||||||
|
name: nginx |
||||||
|
vars: |
||||||
|
nginx: |
||||||
|
servers: |
||||||
|
- conf: nginx_crl |
||||||
|
http: true |
||||||
|
- conf: nginx_crl |
||||||
|
certs: true |
||||||
|
|
||||||
|
|
||||||
|
- name: create crl directory |
||||||
|
file: |
||||||
|
path: "{{ crl_dir }}" |
||||||
|
state: directory |
||||||
|
mode: 0500 |
||||||
|
owner: nginx |
||||||
|
group: nginx |
||||||
|
|
||||||
|
|
||||||
|
- name: generate crls |
||||||
|
include_role: |
||||||
|
name: ca |
||||||
|
vars: |
||||||
|
function: crl |
||||||
|
ca_options: |
||||||
|
path: "{{ crl_dir }}" |
||||||
|
mode: '0400' |
||||||
|
owner: nginx |
||||||
|
group: nginx |
||||||
|
ca_crls: |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ crl_dir }}" |
@ -0,0 +1,4 @@ |
|||||||
|
location / { |
||||||
|
root {{ crl_dir }}; |
||||||
|
try_files $uri =404; |
||||||
|
} |
@ -0,0 +1,290 @@ |
|||||||
|
dovecot_user: dovecot |
||||||
|
dovecot_group: dovecot |
||||||
|
dovecot_mail_user: dovemail |
||||||
|
dovecot_mail_group: dovemail |
||||||
|
dovecot_null_user: dovenull |
||||||
|
|
||||||
|
dovecot_conf_dir: /etc/dovecot |
||||||
|
dovecot_tls_dir: "{{ dovecot_conf_dir }}/tls" |
||||||
|
dovecot_sieve_dir: "{{ dovecot_conf_dir }}/sieve" |
||||||
|
dovecot_mail_dir: /opt/mail |
||||||
|
dovecot_script_dir: "{{ dovecot_conf_dir }}/scripts" |
||||||
|
|
||||||
|
dovecot_tls_dh2048: "{{ dovecot_tls_dir }}/dh2048.pem" |
||||||
|
dovecot_tls_int_ecc384_key: "{{ dovecot_tls_dir }}/ecc384.key" |
||||||
|
dovecot_tls_int_ecc384_cert: "{{ dovecot_tls_dir }}/ecc384.crt" |
||||||
|
dovecot_tls_int_rsa2048_key: "{{ dovecot_tls_dir }}/rsa2048.key" |
||||||
|
dovecot_tls_int_rsa2048_cert: "{{ dovecot_tls_dir }}/rsa2048.crt" |
||||||
|
|
||||||
|
|
||||||
|
dovecot_drafts_name: Drafts |
||||||
|
dovecot_junk_name: Junk |
||||||
|
dovecot_sent_name: Sent |
||||||
|
dovecot_trash_name: Trash |
||||||
|
dovecot_expunged_name: .EXPUNGED |
||||||
|
|
||||||
|
dovecot_max_quota_mb: 5000 |
||||||
|
|
||||||
|
dovecot_default_config: |
||||||
|
protocols: imap lmtp sieve |
||||||
|
hostname: "{{ (mail_server.mua_actual_hostname | d(host_name)) ~ '@' ~ mail_server.tld }}" |
||||||
|
login_greeting: "IMAPS {{ org }} (Dovecot) ready" |
||||||
|
|
||||||
|
auth_cache_ttl: 20m |
||||||
|
auth_cache_size: 2M |
||||||
|
auth_cache_negative_ttl: 5m |
||||||
|
auth_mechanisms: |
||||||
|
- plain |
||||||
|
- login |
||||||
|
- digest-md5 |
||||||
|
- cram-md5 |
||||||
|
- scram-sha-1 |
||||||
|
- scram-sha-256 |
||||||
|
auth_default_realm: "{{ mail_server.tld }}" |
||||||
|
auth_realms: "{{ mail_server.tld }}" |
||||||
|
auth_worker_max_count: 5 |
||||||
|
|
||||||
|
default_internal_user: "{{ dovecot_user }}" |
||||||
|
default_internal_group: "{{ dovecot_group }}" |
||||||
|
default_login_user: "{{ dovecot_null_user }}" |
||||||
|
default_process_limit: 50 |
||||||
|
default_vsz_limit: 64M |
||||||
|
|
||||||
|
disable_plaintext_auth: yes |
||||||
|
|
||||||
|
imap_capability: "+SPECIAL-USE" |
||||||
|
imap_id_send: '"name" * "version" * support-email postmaster@{{ mail_server.tld }}' |
||||||
|
|
||||||
|
mail_attachment_detection_options: add-flags |
||||||
|
mail_attribute_dict: "file:%h/mail_attrib" |
||||||
|
mail_gid: "{{ dovecot_mail_group }}" |
||||||
|
mail_home: "{{ dovecot_mail_dir }}/%Ld/%Ln" |
||||||
|
mail_location: "mdbox:%h/mail:UTF-8" |
||||||
|
mail_max_keyword_length: 100 |
||||||
|
mail_server_admin: "mailto:{{ maintainer_email }}" |
||||||
|
mail_server_comment: "Dovecot IMAPS server - {{ org }}" |
||||||
|
mail_temp_scan_interval: 24h |
||||||
|
mail_uid: "{{ dovecot_mail_user }}" |
||||||
|
|
||||||
|
postmaster_address: "postmaster@{{ mail_server.tld }}" |
||||||
|
quota_full_tempfail: yes |
||||||
|
recipient_delimiter: '+' |
||||||
|
submission_client_workarounds: whitespace-before-path mailbox-for-path |
||||||
|
|
||||||
|
ssl: required |
||||||
|
ssl_cert: "<{{ dovecot_tls_int_ecc384_cert }}" |
||||||
|
ssl_key: "<{{ dovecot_tls_int_ecc384_key }}" |
||||||
|
ssl_alt_cert: "<{{ dovecot_tls_int_rsa2048_cert }}" |
||||||
|
ssl_alt_key: "<{{ dovecot_tls_int_rsa2048_key }}" |
||||||
|
ssl_cipher_suites: "TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256" |
||||||
|
ssl_dh: "<{{ dovecot_tls_dh2048 }}" |
||||||
|
ssl_min_protocol: TLSv1.2 |
||||||
|
ssl_prefer_server_ciphers: yes |
||||||
|
|
||||||
|
mail_plugins: "$mail_plugins mailbox_alias lazy_expunge listescape trash quota acl" |
||||||
|
|
||||||
|
|
||||||
|
dovecot_protocols: |
||||||
|
imap: |
||||||
|
imap_metadata: yes |
||||||
|
mail_plugins: "$mail_plugins imap_zlib imap_quota imap_acl imap_sieve" |
||||||
|
lmtp: |
||||||
|
mail_plugins: "$mail_plugins sieve" |
||||||
|
lmtp_client_workarounds: whitespace-before-path mailbox-for-path |
||||||
|
lmtp_user_concurrency_limit: 25 |
||||||
|
lda: |
||||||
|
mail_plugins: "$mail_plugins sieve" |
||||||
|
lda_mailbox_autocreate: yes |
||||||
|
lda_mailbox_autosubscribe: yes |
||||||
|
sieve: |
||||||
|
mail_max_userip_connections: 50 |
||||||
|
|
||||||
|
|
||||||
|
dovecot_namespaces: |
||||||
|
- name: inbox |
||||||
|
opts: |
||||||
|
inbox: yes |
||||||
|
separator: '/' |
||||||
|
|
||||||
|
mailboxes: |
||||||
|
- name: INBOX |
||||||
|
opts: |
||||||
|
auto: subscribe |
||||||
|
|
||||||
|
- name: "{{ dovecot_drafts_name }}" |
||||||
|
opts: |
||||||
|
auto: subscribe |
||||||
|
special_use: '\Drafts' |
||||||
|
|
||||||
|
- name: "{{ dovecot_junk_name }}" |
||||||
|
opts: |
||||||
|
auto: subscribe |
||||||
|
special_use: '\Junk' |
||||||
|
autoexpunge: 90d |
||||||
|
|
||||||
|
- name: "{{ dovecot_sent_name }}" |
||||||
|
opts: |
||||||
|
auto: subscribe |
||||||
|
special_use: '\Sent' |
||||||
|
|
||||||
|
- name: "{{ dovecot_trash_name }}" |
||||||
|
opts: |
||||||
|
auto: subscribe |
||||||
|
special_use: '\Trash' |
||||||
|
autoexpunge: 90d |
||||||
|
|
||||||
|
- name: "{{ dovecot_expunged_name }}" |
||||||
|
opts: |
||||||
|
auto: create |
||||||
|
autoexpunge: 180d |
||||||
|
|
||||||
|
- name: shared |
||||||
|
opts: |
||||||
|
type: shared |
||||||
|
separator: '/' |
||||||
|
prefix: 'Общие/%%u/' |
||||||
|
location: 'mdbox:%%h/mail:INDEXPVT=%h/shared_idx/%%u' |
||||||
|
subscriptions: no |
||||||
|
list: children |
||||||
|
|
||||||
|
|
||||||
|
dovecot_dicts: |
||||||
|
acl: "pgsql:{{ dovecot_conf_dir }}/dovecot-dict-sql.conf.ext" |
||||||
|
|
||||||
|
|
||||||
|
dovecot_plugin_config: |
||||||
|
trash: "{{ dovecot_conf_dir }}/dovecot-trash.conf.ext" |
||||||
|
|
||||||
|
lazy_expunge: "{{ dovecot_expunged_name }}" |
||||||
|
lazy_expunge_only_last_instance: yes |
||||||
|
|
||||||
|
acl: "vfile:{{ dovecot_conf_dir }}/dovecot.acl" |
||||||
|
acl_shared_dict: "proxy::acl" |
||||||
|
|
||||||
|
quota: "count:Account quota" |
||||||
|
quota_exceeded_message: Mailbox quota exceeded |
||||||
|
quota_grace: "5%%" |
||||||
|
quota_max_mail_size: "{{ mail_server.max_mail_size_bytes ~ 'B' }}" |
||||||
|
quota_rule: "*:storage={{ dovecot_max_quota_mb }}M" |
||||||
|
quota_rule2: "{{ dovecot_trash_name }}:storage=+200M" |
||||||
|
quota_rule3: "{{ dovecot_expunged_name }}:ignore" |
||||||
|
quota_status_success: DUNNO |
||||||
|
quota_status_nouser: DUNNO |
||||||
|
quota_status_overquota: "452 4.2.2 User mailbox is full" |
||||||
|
quota_vsizes: yes |
||||||
|
|
||||||
|
sieve_extensions: "-enotify -editheader" |
||||||
|
sieve_global_extensions: "+vnd.dovecot.pipe +vnd.dovecot.filter +vnd.dovecot.execute" |
||||||
|
sieve_max_actions: 64 |
||||||
|
sieve_plugins: sieve_imapsieve sieve_extprograms |
||||||
|
|
||||||
|
sieve_pipe_bin_dir: "{{ dovecot_script_dir }}" |
||||||
|
sieve_execute_bin_dir: "{{ dovecot_script_dir }}" |
||||||
|
sieve_filter_bin_dir: "{{ dovecot_script_dir }}" |
||||||
|
|
||||||
|
sieve_spamtest_status_type: text |
||||||
|
sieve_spamtest_status_header: X-Spam |
||||||
|
sieve_spamtest_text_value0: No |
||||||
|
sieve_spamtest_text_value10: Yes |
||||||
|
|
||||||
|
sieve_before: "{{ dovecot_sieve_dir }}/spam-to-folder.sieve" |
||||||
|
|
||||||
|
|
||||||
|
dovecot_user_pass_db: |
||||||
|
- type: passdb |
||||||
|
opts: |
||||||
|
driver: sql |
||||||
|
args: "{{ dovecot_conf_dir }}/dovecot-sql.conf.ext" |
||||||
|
- type: userdb |
||||||
|
opts: |
||||||
|
driver: prefetch |
||||||
|
- type: userdb |
||||||
|
opts: |
||||||
|
driver: sql |
||||||
|
args: "{{ dovecot_conf_dir }}/dovecot-sql.conf.ext" |
||||||
|
|
||||||
|
|
||||||
|
dovecot_services: |
||||||
|
imap: |
||||||
|
opts: |
||||||
|
service_count: 16 |
||||||
|
process_limit: 256 |
||||||
|
|
||||||
|
imap-login: |
||||||
|
opts: |
||||||
|
service_count: 0 |
||||||
|
process_min_avail: 1 |
||||||
|
client_limit: 16 |
||||||
|
service_count: 32 |
||||||
|
|
||||||
|
listeners: |
||||||
|
- type: inet_listener |
||||||
|
name: imap |
||||||
|
opts: |
||||||
|
port: 143 |
||||||
|
|
||||||
|
- type: inet_listener |
||||||
|
name: imaps |
||||||
|
opts: |
||||||
|
port: 993 |
||||||
|
ssl: yes |
||||||
|
|
||||||
|
lmtp: |
||||||
|
opts: |
||||||
|
client_limit: 1 |
||||||
|
vsz_limit: 192M |
||||||
|
|
||||||
|
listeners: |
||||||
|
- type: inet_listener |
||||||
|
opts: |
||||||
|
port: "{{ mail_server.mua_lmtp_port }}" |
||||||
|
|
||||||
|
auth: |
||||||
|
listeners: |
||||||
|
- type: inet_listener |
||||||
|
opts: |
||||||
|
port: "{{ mail_server.mua_auth_port }}" |
||||||
|
- type: unix_listener auth-userdb |
||||||
|
opts: |
||||||
|
mode: 0666 |
||||||
|
user: "{{ dovecot_user }}" |
||||||
|
group: "{{ dovecot_group }}" |
||||||
|
|
||||||
|
quota-status: |
||||||
|
opts: |
||||||
|
executable: "/usr/libexec/dovecot/quota-status -p postfix" |
||||||
|
|
||||||
|
listeners: |
||||||
|
- type: inet_listener |
||||||
|
opts: |
||||||
|
port: "{{ mail_server.mua_quota_port }}" |
||||||
|
|
||||||
|
auth-worker: |
||||||
|
opts: |
||||||
|
user: "{{ dovecot_user }}" |
||||||
|
group: "{{ dovecot_group }}" |
||||||
|
|
||||||
|
dict: |
||||||
|
opts: |
||||||
|
user: "{{ dovecot_user }}" |
||||||
|
group: "{{ dovecot_group }}" |
||||||
|
listeners: |
||||||
|
- type: unix_listener dict |
||||||
|
opts: |
||||||
|
mode: 0666 |
||||||
|
user: "{{ dovecot_user }}" |
||||||
|
group: "{{ dovecot_group }}" |
||||||
|
|
||||||
|
managesieve-login: |
||||||
|
opts: |
||||||
|
service_count: 0 |
||||||
|
process_min_avail: 1 |
||||||
|
|
||||||
|
managesieve: |
||||||
|
opts: |
||||||
|
process_limit: 512 |
||||||
|
|
||||||
|
|
||||||
|
dovecot_sieve_scripts: |
||||||
|
- src: sieve-spam |
||||||
|
dest: spam-to-folder |
@ -0,0 +1,4 @@ |
|||||||
|
- name: restart dovecot |
||||||
|
service: |
||||||
|
name: dovecot |
||||||
|
state: restarted |
@ -0,0 +1,241 @@ |
|||||||
|
- name: set dovecot_cfg |
||||||
|
set_fact: |
||||||
|
dovecot_cfg: "{{ dovecot_default_config | d({}) | combine(dovecot_config | d({}), recursive=true) }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: install dovecot |
||||||
|
include_tasks: tasks/install_packages.yml |
||||||
|
vars: |
||||||
|
package: |
||||||
|
- dovecot |
||||||
|
- dovecot-lmtpd |
||||||
|
- dovecot-openrc |
||||||
|
- dovecot-pgsql |
||||||
|
- dovecot-pigeonhole-plugin |
||||||
|
|
||||||
|
|
||||||
|
- name: create user and group |
||||||
|
include_tasks: tasks/create_user.yml |
||||||
|
vars: |
||||||
|
user: |
||||||
|
name: "{{ dovecot_user }}" |
||||||
|
group: "{{ dovecot_group }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: create dovemail user and group |
||||||
|
include_tasks: tasks/create_user.yml |
||||||
|
vars: |
||||||
|
user: |
||||||
|
name: "{{ dovecot_mail_user }}" |
||||||
|
group: "{{ dovecot_mail_group }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: create dovenull user and group |
||||||
|
include_tasks: tasks/create_user.yml |
||||||
|
vars: |
||||||
|
user: |
||||||
|
name: "{{ dovecot_null_user }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: create dovecot conf dir |
||||||
|
file: |
||||||
|
path: "{{ dovecot_conf_dir }}" |
||||||
|
state: directory |
||||||
|
mode: 0755 |
||||||
|
owner: "{{ dovecot_user }}" |
||||||
|
group: "{{ dovecot_group }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: create dovecot tls dir |
||||||
|
file: |
||||||
|
path: "{{ dovecot_tls_dir }}" |
||||||
|
state: directory |
||||||
|
mode: 0700 |
||||||
|
|
||||||
|
|
||||||
|
- name: create dovecot mail dir |
||||||
|
file: |
||||||
|
path: "{{ dovecot_mail_dir }}" |
||||||
|
state: directory |
||||||
|
mode: "g+s,o-rwx" |
||||||
|
owner: "{{ dovecot_mail_user }}" |
||||||
|
group: "{{ dovecot_mail_group }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: create dovecot sieve dir |
||||||
|
file: |
||||||
|
path: "{{ dovecot_sieve_dir }}" |
||||||
|
state: directory |
||||||
|
mode: 0755 |
||||||
|
owner: "{{ dovecot_mail_user }}" |
||||||
|
group: "{{ dovecot_mail_group }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: generate dh params |
||||||
|
include_role: |
||||||
|
name: ca |
||||||
|
vars: |
||||||
|
function: dhparams |
||||||
|
dh_params: |
||||||
|
path: "{{ dovecot_tls_dh2048 }}" |
||||||
|
mode: '0400' |
||||||
|
remote_gen: yes |
||||||
|
notify: restart dovecot |
||||||
|
|
||||||
|
|
||||||
|
- name: remove unneeded dovecot files |
||||||
|
file: |
||||||
|
path: "{{ dovecot_conf_dir ~ '/' ~ item }}" |
||||||
|
state: absent |
||||||
|
loop: |
||||||
|
- conf.d |
||||||
|
- dovecot-dict-auth.conf.ext |
||||||
|
- dovecot-oauth2.conf.ext |
||||||
|
- dovecot-openssl.cnf |
||||||
|
- users |
||||||
|
notify: restart dovecot |
||||||
|
|
||||||
|
|
||||||
|
- name: get dovemail user info |
||||||
|
getent: |
||||||
|
database: passwd |
||||||
|
key: "{{ dovecot_mail_user }}" |
||||||
|
changed_when: no |
||||||
|
|
||||||
|
|
||||||
|
- name: set dovemail uid |
||||||
|
set_fact: |
||||||
|
dovecot_dovemail_uid: "{{ getent_passwd[dovecot_mail_user][1] }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: template dovecot configuration |
||||||
|
template: |
||||||
|
src: "{{ item if item is string else item.src }}.j2" |
||||||
|
dest: "{{ dovecot_conf_dir ~ '/' ~ ((item ~ '.conf.ext') if item is string else item.dest) }}" |
||||||
|
force: yes |
||||||
|
mode: "{{ '0400' if (item is string) else (item.mode | d('0400')) }}" |
||||||
|
lstrip_blocks: yes |
||||||
|
loop: |
||||||
|
- { src: dovecot-dict-sql, dest: dovecot-dict-sql.conf.ext, mode: '0444' } |
||||||
|
- dovecot-sql |
||||||
|
- dovecot-trash |
||||||
|
- { src: dovecot-acl, dest: dovecot.acl } |
||||||
|
- { src: dovecot, dest: dovecot.conf } |
||||||
|
notify: restart dovecot |
||||||
|
|
||||||
|
|
||||||
|
- name: edit permissions of dovecot plugin files |
||||||
|
file: |
||||||
|
path: "{{ dovecot_conf_dir ~ '/' ~ item }}" |
||||||
|
state: file |
||||||
|
owner: "{{ dovecot_mail_user }}" |
||||||
|
group: "{{ dovecot_mail_group }}" |
||||||
|
loop: |
||||||
|
- dovecot.acl |
||||||
|
- dovecot-sql.conf.ext |
||||||
|
- dovecot-trash.conf.ext |
||||||
|
- dovecot-dict-sql.conf.ext |
||||||
|
notify: restart dovecot |
||||||
|
|
||||||
|
|
||||||
|
- name: template sieve scripts |
||||||
|
template: |
||||||
|
src: "{{ item.src }}.j2" |
||||||
|
dest: "{{ dovecot_sieve_dir ~ '/' ~ item.dest ~ '.sieve' }}" |
||||||
|
force: yes |
||||||
|
mode: 0400 |
||||||
|
owner: "{{ dovecot_mail_user }}" |
||||||
|
group: "{{ dovecot_mail_group }}" |
||||||
|
loop: "{{ dovecot_sieve_scripts | d([]) }}" |
||||||
|
register: result |
||||||
|
|
||||||
|
|
||||||
|
- name: compile scripts |
||||||
|
shell: |
||||||
|
cmd: "sievec {{ (dovecot_sieve_dir ~ '/') | quote }}" |
||||||
|
when: result.changed |
||||||
|
notify: restart dovecot |
||||||
|
|
||||||
|
|
||||||
|
- name: collect svbin files |
||||||
|
find: |
||||||
|
paths: "{{ dovecot_sieve_dir }}/" |
||||||
|
patterns: "*.svbin" |
||||||
|
recurse: yes |
||||||
|
depth: 3 |
||||||
|
register: svbin_files |
||||||
|
|
||||||
|
|
||||||
|
- name: change svbin permissions |
||||||
|
file: |
||||||
|
path: "{{ item.path }}" |
||||||
|
mode: 0400 |
||||||
|
owner: "{{ dovecot_mail_user }}" |
||||||
|
group: "{{ dovecot_mail_group }}" |
||||||
|
loop: "{{ svbin_files.files | d([]) | flatten(levels=1) }}" |
||||||
|
notify: restart dovecot |
||||||
|
|
||||||
|
|
||||||
|
- name: add extra cname record |
||||||
|
include_role: |
||||||
|
name: ns |
||||||
|
vars: |
||||||
|
function: add_records |
||||||
|
ns_add_default_record: no |
||||||
|
ns_records: |
||||||
|
- name: "{{ mail_server.mua_actual_hostname }}" |
||||||
|
type: CNAME |
||||||
|
value: "{{ host_fqdn }}" |
||||||
|
when: mail_server.mua_actual_hostname is defined |
||||||
|
|
||||||
|
|
||||||
|
- name: deploy certs |
||||||
|
include_role: |
||||||
|
name: certs |
||||||
|
vars: |
||||||
|
common: |
||||||
|
owner: root |
||||||
|
group: root |
||||||
|
post_hook: service dovecot restart |
||||||
|
notify: restart dovecot |
||||||
|
hostname: "{{ mail_server.mua_actual_hostname }}" |
||||||
|
certs: |
||||||
|
- cert: "{{ dovecot_tls_int_ecc384_cert }}" |
||||||
|
key: "{{ dovecot_tls_int_ecc384_key }}" |
||||||
|
ecc: yes |
||||||
|
- cert: "{{ dovecot_tls_int_rsa2048_cert }}" |
||||||
|
key: "{{ dovecot_tls_int_rsa2048_key }}" |
||||||
|
ecc: no |
||||||
|
|
||||||
|
|
||||||
|
- name: flush handlers |
||||||
|
meta: flush_handlers |
||||||
|
|
||||||
|
|
||||||
|
- name: add directories to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ dovecot_conf_dir }}" |
||||||
|
- "{{ dovecot_tls_dir }}" |
||||||
|
- "{{ dovecot_sieve_dir }}" |
||||||
|
- "{{ dovecot_script_dir }}" |
||||||
|
|
||||||
|
|
||||||
|
- name: add mail dir to backup plan |
||||||
|
include_role: |
||||||
|
name: backup |
||||||
|
vars: |
||||||
|
function: add |
||||||
|
backup_items: |
||||||
|
- "{{ dovecot_mail_dir }}" |
||||||
|
when: dovecot_backup_mail_dir | d(false) == true |
||||||
|
|
||||||
|
|
||||||
|
- name: enable and start dovecot |
||||||
|
service: |
||||||
|
name: dovecot |
||||||
|
enabled: yes |
||||||
|
state: started |
@ -0,0 +1,8 @@ |
|||||||
|
* user={{ mail_server.admin_email }} lrwstipekxa |
||||||
|
INBOX owner lrwstipek |
||||||
|
{{ dovecot_sent_name }} owner lrwstipek |
||||||
|
{{ dovecot_drafts_name }} owner lrwstipek |
||||||
|
{{ dovecot_junk_name }} owner lrwstipek |
||||||
|
{{ dovecot_trash_name }} owner lrwstipek |
||||||
|
{{ dovecot_expunged_name }} owner |
||||||
|
{{ dovecot_expunged_name }} anyone |
@ -0,0 +1,22 @@ |
|||||||
|
connect = host={{ hostvars[mail_server.db_server_hostname]['ansible_host'] }} user={{ mail_server.db_user }} password={{ mail_server.db_pass }} dbname={{ mail_server.db_name }} |
||||||
|
|
||||||
|
map { |
||||||
|
pattern = shared/shared-boxes/user/$to/$from |
||||||
|
table = mail_user_shares |
||||||
|
value_field = dummy |
||||||
|
|
||||||
|
fields { |
||||||
|
from_user = $from |
||||||
|
to_user = $to |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
map { |
||||||
|
pattern = shared/shared-boxes/anyone/$from |
||||||
|
table = mail_anyone_shares |
||||||
|
value_field = dummy |
||||||
|
|
||||||
|
fields { |
||||||
|
from_user = $from |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
driver = pgsql |
||||||
|
connect = host={{ hostvars[mail_server.db_server_hostname]['ansible_host'] }} user={{ mail_server.db_user }} password={{ mail_server.db_pass }} dbname={{ mail_server.db_name }} |
||||||
|
default_pass_scheme = PLAIN |
||||||
|
|
||||||
|
password_query = \ |
||||||
|
SELECT username AS user, \ |
||||||
|
( \ |
||||||
|
SELECT domain FROM mail_domains WHERE id = domain_id \ |
||||||
|
) AS domain, \ |
||||||
|
password_plaintext AS password, \ |
||||||
|
'{{ dovecot_mail_dir }}/%Ld/%Ln' AS userdb_home, \ |
||||||
|
concat('*:bytes=', coalesce(nullif(quota_mb, 0), {{ dovecot_max_quota_mb }}), 'M') AS userdb_quota_rule, \ |
||||||
|
{{ dovecot_dovemail_uid }} AS userdb_uid \ |
||||||
|
FROM mail_users \ |
||||||
|
WHERE \ |
||||||
|
LOWER(username) = '%Ln' AND \ |
||||||
|
domain_id = ( \ |
||||||
|
SELECT id FROM mail_domains WHERE LOWER(domain) = '%Ld' \ |
||||||
|
) AND \ |
||||||
|
enabled = true; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
user_query = \ |
||||||
|
SELECT username AS user, \ |
||||||
|
( \ |
||||||
|
SELECT domain FROM mail_domains WHERE id = domain_id \ |
||||||
|
) AS domain, \ |
||||||
|
'{{ dovecot_mail_dir }}/%Ld/%Ln' AS home, \ |
||||||
|
concat('*:bytes=', coalesce(nullif(quota_mb, 0), {{ dovecot_max_quota_mb }}), 'M') AS quota_rule, \ |
||||||
|
{{ dovecot_dovemail_uid }} AS uid \ |
||||||
|
FROM mail_users \ |
||||||
|
WHERE \ |
||||||
|
LOWER(username) = '%Ln' AND \ |
||||||
|
domain_id = ( \ |
||||||
|
SELECT id FROM mail_domains WHERE LOWER(domain) = '%Ld' \ |
||||||
|
) AND \ |
||||||
|
enabled = true; |
||||||
|
|
||||||
|
|
||||||
|
iterate_query = \ |
||||||
|
SELECT username AS user, \ |
||||||
|
( \ |
||||||
|
SELECT domain FROM mail_domains WHERE id = domain_id \ |
||||||
|
) AS domain \ |
||||||
|
FROM mail_users \ |
||||||
|
WHERE enabled = true; |
@ -0,0 +1,3 @@ |
|||||||
|
1 {{ dovecot_trash_name }} |
||||||
|
2 {{ dovecot_junk_name }} |
||||||
|
3 {{ dovecot_sent_name }} |
@ -0,0 +1,94 @@ |
|||||||
|
{% macro dovecot_option(option, padding = 0) -%} |
||||||
|
{{- '' if (padding == 0) else (' ' * 4 * padding) -}} |
||||||
|
{% if option.value is boolean -%} |
||||||
|
{{ option.key }} = {{ 'yes' if option.value else 'no' }} |
||||||
|
{% elif option.value | type_debug == 'list' -%} |
||||||
|
{{ option.key }} = {{ option.value | join(' ') }} |
||||||
|
{% elif option.value is mapping -%} |
||||||
|
{{ option.key }} { |
||||||
|
{% for suboption in (option.value | d({}) | dict2items) -%} |
||||||
|
{{- dovecot_option(suboption, padding + 1) }} |
||||||
|
{% endfor -%} |
||||||
|
} |
||||||
|
{% else -%} |
||||||
|
{{ option.key }} = {{ option.value if option.value != None else '' }} |
||||||
|
{% endif -%} |
||||||
|
{% endmacro -%} |
||||||
|
|
||||||
|
|
||||||
|
{% for option in (dovecot_cfg | d({}) | dict2items) -%} |
||||||
|
{{ dovecot_option(option) }} |
||||||
|
{%- endfor %} |
||||||
|
|
||||||
|
first_valid_uid = {{ dovecot_dovemail_uid }} |
||||||
|
last_valid_uid = {{ dovecot_dovemail_uid }} |
||||||
|
|
||||||
|
|
||||||
|
{% for proto in (dovecot_protocols | d({}) | dict2items) -%} |
||||||
|
protocol {{ proto.key }} { |
||||||
|
{% for option in (proto.value | d({}) | dict2items) -%} |
||||||
|
{{ dovecot_option(option, 1) }} |
||||||
|
{%- endfor -%} |
||||||
|
} |
||||||
|
{% endfor %} |
||||||
|
|
||||||
|
|
||||||
|
{% for namespace in (dovecot_namespaces | d([])) -%} |
||||||
|
namespace {{ namespace.name }} { |
||||||
|
{% for option in (namespace.opts | d({}) | dict2items) -%} |
||||||
|
{{ dovecot_option(option, 1) }} |
||||||
|
{%- endfor -%} |
||||||
|
|
||||||
|
{% for mailbox in (namespace.mailboxes | d([])) -%} |
||||||
|
{{- ' ' -}}mailbox {{ mailbox.name }} { |
||||||
|
{% for mailbox_option in (mailbox.opts | d({}) | dict2items) -%} |
||||||
|
{{ dovecot_option(mailbox_option, 2) }} |
||||||
|
{%- endfor -%} |
||||||
|
{{- ' ' -}}} |
||||||
|
{% endfor -%} |
||||||
|
} |
||||||
|
{% endfor %} |
||||||
|
|
||||||
|
|
||||||
|
{% if dovecot_dicts is mapping -%} |
||||||
|
dict { |
||||||
|
{% for option in (dovecot_dicts | d({}) | dict2items) -%} |
||||||
|
{{ dovecot_option(option, 1) }} |
||||||
|
{%- endfor -%} |
||||||
|
} |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
|
||||||
|
{% if dovecot_plugin_config is mapping -%} |
||||||
|
plugin { |
||||||
|
{% for option in (dovecot_plugin_config | d({}) | dict2items) -%} |
||||||
|
{{ dovecot_option(option, 1) }} |
||||||
|
{%- endfor -%} |
||||||
|
} |
||||||
|
{% endif %} |
||||||
|
|
||||||
|
|
||||||
|
{% for db in (dovecot_user_pass_db | d([])) -%} |
||||||
|
{{ db.type }} { |
||||||
|
{% for option in (db.opts | d({}) | dict2items) -%} |
||||||
|
{{ dovecot_option(option, 1) }} |
||||||
|
{%- endfor -%} |
||||||
|
} |
||||||
|
{% endfor %} |
||||||
|
|
||||||
|
|
||||||
|
{% for service in (dovecot_services | d({}) | dict2items) -%} |
||||||
|
service {{ service.key }} { |
||||||
|
{% for option in (service.value.opts | d({}) | dict2items) -%} |
||||||
|
{{ dovecot_option(option, 1) }} |
||||||
|
{%- endfor %} |
||||||
|
|
||||||
|
{% for listener in (service.value.listeners | d([])) -%} |
||||||
|
{{- ' ' -}}{{ listener.type }} {{ listener.name | d('') }} { |
||||||
|
{% for listener_option in (listener.opts | d({}) | dict2items) -%} |
||||||
|
{{ dovecot_option(listener_option, 2) }} |
||||||
|
{%- endfor -%} |
||||||
|
{{- ' ' -}}} |
||||||
|
{% endfor -%} |
||||||
|
} |
||||||
|
{% endfor %} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue