initial commit

master
HgO 2020-04-13 14:46:45 +02:00
commit 961498e32b
76 changed files with 2715 additions and 0 deletions

1
.gitignore vendored 100644
View File

@ -0,0 +1 @@
.vscode

View File

@ -0,0 +1,45 @@
openssh_port: "12322"
smtp_accounts:
- name: ahoy
host: mail.infomaniak.ch
port: 587
from: ahoy@pirateparty.be
password: !vault |
$ANSIBLE_VAULT;1.1;AES256
62633164383764376333643063363263356461613164663066623836613931306437633033633134
3632326164663564653962613437376265333234313032360a313935303230393938356632356231
34613661383736313232613131313262616261323464653936393634653464323631333839353030
3230396536663635650a633537633633623365346563323334616338333436633537623831313165
38343766346437626332313230346537663735313937643765353465356236633134
smtp_default_account: ahoy
smtp_default_contact: it@pirateparty.be
node_exporter_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
35333237666635633862336433303264613133376230346461333332636231643563636466356630
3235623237303366626562393065353436663632306438370a333132653432636632643134326130
66396666626631373637373065613137393232383361346438633763396266636264663364663238
3363666332633562360a323532666664333266333761343136306133336138623137316234653939
37643239613631383165656138633134663736393238343939336135303732333838336538373531
3635396265643061356339333035393836313936316633623662
backup_targets:
- host: storage.pirateparty.be
ssh:
port: 23
username: "{{ storage_box_username }}"
key_file: storage-box
- host: batato.be
ssh:
key_file: batato
users:
- name: hgo
ssh_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOK8Y3OEq1j3rR8EOLpVPYZeA5qC0PTsctza9c2qhbU hadrien@terry
- name: tierce
ssh_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC75IeAMEg6RwvbO6oLEQOpYSASGx9A3JD15gtA7D3NJFz+pZ7hBjBSjTxZrHDQLg1OFs0XRGS5DATRMnj6jSRAc25C71DewbNY9fWOH1/dAuo45zBllO3/pol17uYVqUbaPVjnqQFfikLCf7HjBbjt7JEVffJ3nkalE2q0TqjGK0JrltrL9dePE/R3ZNzVSDXvkgMsu18Wov9if6ftsKYgNTW+oOc9xoN1GSHYEnzv68+YNt3zKGTiwhU87cLyHJBu9o/wFDNOLdQcOtKa3IUPZvOgDlLrAm8a4Z9/A9DCJS/8kFmyNOazF1rupPAojn7k9mIBvVPxc5zqg+qrKbxR tierce@q
acme_email: it@pirateparty.be

View File

@ -0,0 +1,41 @@
alertmanager_smtp:
from: ahoy@pirateparty.be
smarthost: mail.infomaniak.ch:587
auth_username: ahoy@pirateparty.be
auth_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
61643536623562333434653364623535633331653539356132653863313965313030333163313637
6161333463653839383265323937376630336134633531650a313132326536346530353764656465
63323737643034353532333363303363616261363335333365663133626537653961323133626433
6566656236383864610a323262393562663836343162326131336630363939356333313934326436
6261
alertmanager_route:
receiver: 'default-receiver'
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
alertmanager_receivers:
- name: default-receiver
email_configs:
- to: hadrien@pirateparty.be
alertmanager_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
63313035633034643636326230383162666666626539623934303631366236656432616238356362
6665626364643666343737623532616133303539356133300a396530643865323334313564363762
31646562306232356437636537383732626664663166656331303630303537383064663565323235
3962313936613039320a656337356131363636643366393233613462313361323639373363643134
32383436313035323032656266376664383166633631663438316165313930373937636436633962
6131336262343531643264346362343433373165386266323439
prometheus_password: "{{ alertmanager_password }}"
grafana_admin_user: ppbe
grafana_admin_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
39626461326636633230343536613564643537376464336537353661636638303238303966383030
3133663938623334396435333761306265373064353462610a356531326130396566386638653533
36323833663030663466356538353237376137313135656534383439613935623234373065376530
3864366438626135300a333664313339343964306538343366306639393631666366323537313734
36613731626439646537653565646436323839383930363131653431306431396638613665616464
3435313137313964636139366439336365663564326639303234

View File

@ -0,0 +1,46 @@
umurmur_version: 0.2.17
umurmur_domain: mumble.parley.be
umurmur_welcome_text:
- Welcome to Parley Talk!
- You can talk to the people in the room you joined.
- You start in the Welcome room, to join another channel right click the room name and choose "Join Channel"
umurmur_admin_password: wC7yZ4vV2ocb7AkBfQ2RwuhRqYVyiwY42Rjpw3pfJ
umurmur_max_users: 100
umurmur_channels:
- name: Parley
description: Main Parley Talk channel. No entry.
noenter: yes
- name: Welcome
parent: Parley
description: Welcome channel
position: 0
- name: Silent
parent: Parley
description: Silent channel
silent: yes
position: 1
- name: Mary Read's territory
description: The channel dedicated to Mary Read. She was a Caribbean pirate. Dressed as a man, Mary went to sea and later joined the British army, fighting in the War Of The Spanish Succession. She married and settled down as a woman, but dressed back as a man following the death of her husband, later boarding a ship bound for the West Indies.
parent: Welcome
- name: Anne Bonny crew
description: The channel dedicated to Anne Bonny. She was one the most famous female pirates. She operated in the Caribbean. She discovered that one of her crew companion, Mark Read, was secretly a woman (Mary Read) and the two became very close.
parent: Welcome
- name: Mary Cricket's ship
description: The channel dedicated to Mary Cricket. Toghether with 5 other prisoners Mary Crickett escaped and overpowered the two-man crew of the sloop John and Elizabeth on 12 May 1729. She held the prisoners in the ship's hold, sitting on the hatch to prevent their escape. The pair was released a few days later. The pirates sailed into Chesapeake Bay but before they could raid any other ships, they were captured by HMS Shoreham. Returned to Virginia, they were tried in August 1729, convicted of piracy, and sentenced to hang.
parent: Welcome
- name: Flora Burn's island
description: The channel dedicated to Flora Burn. She began her pirate career in 1741 and operated mainly on the East Coast of North America.
parent: Welcome
- name: Sayyida al Hurra
description: She was a Moroccan pirate from the 16th century, and controlled the Mediterranean Sea together with Barbarossa. Her name means "noble lady who is free and independent"
parent: Welcome
- name: Ching Shih
description: She was a Chinese pirate from the 19th century. She is considered to be the pirate with the largest crew ever assembled (between 20.000 and 40.000 pirates) and died peacefully as a free woman
parent: Welcome
umurmur_default_channel: Welcome
mumble_web_domain: talk.parley.be
mumble_web_version: c03b78d096eae69e1cec82e148f65e7a8541bd68

View File

@ -0,0 +1 @@
ansible_user: admin

View File

@ -0,0 +1,8 @@
storage_box_username: u212275-sub5
storage_box_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
36313662333062323531613966386365373339663566303133653562663838316632613830613264
6564333736343830623061313534313630313534316231390a666662633861383563333562356561
64616534313266323833383331313334333761323965383634666635663430366461353437616465
6337363536643738310a656530663837386537336434633037376463336165613239323265366234
64663863333763356430616635323061396663373264343666323831646664646430

View File

@ -0,0 +1,11 @@
[web]
mastodon.pirateparty.be
pirateparty.be
wiki.pirateparty.be
talk.parley.be
[monitoring]
status.pirateparty.be
[mumble]
talk.parley.be

View File

@ -0,0 +1,12 @@
---
- hosts: all
become: yes
pre_tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
roles:
- common

View File

@ -0,0 +1,4 @@
- hosts: monitoring
roles:
- common
- monitoring

View File

@ -0,0 +1,10 @@
- hosts: mumble
become: yes
vars:
acme_domains:
- "{{ umurmur_domain }}"
- "{{ mumble_web_domain }}"
roles:
- acme
- mumble

View File

@ -0,0 +1,9 @@
acme_directory: https://acme-v02.api.letsencrypt.org/directory
acme_config_dir: /etc/ssl
acme_keys_dir: "{{ acme_config_dir }}/private"
acme_csr_dir: "{{ acme_config_dir }}/csr"
acme_certs_dir: "{{ acme_config_dir }}/certs"
acme_accounts_dir: "{{ acme_config_dir }}/accounts"
acme_account_key: "acme_account.key"
acme_ssl_group: ssl-cert
acme_challenge_dir: /var/www/acme

View File

@ -0,0 +1,184 @@
#!/usr/bin/python3
import logging
import os
import sys
from datetime import datetime
import josepy as jose
from OpenSSL import crypto
from acme import client, messages, challenges, crypto_util
SSL_CONFIG_DIR="/etc/ssl"
OPENSSL_DATE_FORMAT = '%Y%m%d%H%M%SZ'
USER_AGENT="python-acme"
def select_http01_challenge(order):
for auth in order.authorizations:
for challenge in auth.body.challenges:
if isinstance(challenge.chall, challenges.HTTP01):
return challenge
raise Exception("HTTP-01 challenge was not offered by the CA server.")
def perform_http01_challenge(client_acme, challenge, order, challenge_dir):
response, validation = challenge.response_and_validation(client_acme.net.key)
validation_filename, validation_content = validation.split('.')
challenge_path = os.path.join(challenge_dir, validation_filename)
with open(challenge_path, 'w') as f_out:
f_out.write(validation_content)
client_acme.answer_challenge(challenge, response)
fullchain_pem = client_acme.poll_and_finalize(order).fullchain_pem
os.remove(challenge_path)
return fullchain_pem
def renew_cert(domain,
account_key_path,
privkey_path,
csr_path,
certs_dir,
challenge_dir,
directory_url,
days_before_renewal,
force = False):
logging.info(f"Checking {domain} certificate's expiration date…")
now = datetime.now()
cert_expiration_date = now
fullchain_path = os.path.join(certs_dir, "fullchain.pem")
try:
with open(fullchain_path, 'rb') as pem_in:
fullchain_pem = crypto.load_certificate(crypto.FILETYPE_PEM, pem_in.read())
cert_expiration_date = datetime.strptime(fullchain_pem.get_notAfter().decode(), OPENSSL_DATE_FORMAT)
except IOError as e:
logging.warning(f"Unable to load {domain} certificate: {e}")
cert_expiration_days = (cert_expiration_date - now).days
if not force and cert_expiration_days >= days_before_renewal:
logging.info(f"Certificate expires in {cert_expiration_days} days. Nothing to do.")
return
logging.info(f"Certificate expires in {cert_expiration_days} days! Renewing certificate…")
logging.info(f"Loading account key from {account_key_path}")
with open(account_key_path, 'rb') as pem_in:
account_pem = crypto.load_privatekey(crypto.FILETYPE_PEM, pem_in.read())
account_jwk = jose.JWKRSA(key=account_pem.to_cryptography_key())
account_jwk_pub = account_jwk.public_key()
logging.info(f"Loading {domain} CSR file from {csr_path}")
try:
with open(csr_path, 'r') as pem_in:
csr_pem = pem_in.read()
except IOError as e:
logging.warning(f"Unable to load CSR file: {e}")
logging.info(f"Loading {domain} private key from {privkey_path}")
with open(privkey_path, 'r') as pem_in:
privkey_pem = pem_in.read()
csr_pem = crypto_util.make_csr(privkey_pem, [domain])
client_network = client.ClientNetwork(account_jwk, user_agent=USER_AGENT)
directory = messages.Directory.from_json(client_network.get(directory_url).json())
client_acme = client.ClientV2(directory, net=client_network)
logging.info("Registering with ACME account…")
# Here we assume we already have an account
registration_message = messages.NewRegistration(
key=account_jwk_pub,
only_return_existing=True)
registration_response = client_acme._post(directory["newAccount"], registration_message)
account = client_acme._regr_from_response(registration_response)
client_acme.net.account = account
logging.info("Ordering ACME challenge…")
order = client_acme.new_order(csr_pem)
logging.info("Selecting HTTP-01 ACME challenge…")
challenge = select_http01_challenge(order)
logging.info("Performing HTTP-01 ACME challenge…")
fullchain_pem = perform_http01_challenge(client_acme, challenge, order, challenge_dir)
logging.info(f"Writing {domain} certificates into {certs_dir}")
certs_pem = []
for line in fullchain_pem.split('\n'):
if 'BEGIN CERTIFICATE' in line:
cert_pem = ''
cert_pem += line + '\n'
if 'END CERTIFICATE' in line:
certs_pem.append(cert_pem)
cert_path = os.path.join(certs_dir, "cert.pem")
with open(cert_path, 'w') as pem_out:
pem_out.write(certs_pem[0])
chain_path = os.path.join(certs_dir, "chain.pem")
with open(chain_path, 'w') as pem_out:
pem_out.write(''.join(certs_pem[1:]))
with open(fullchain_path, 'w') as pem_out:
pem_out.write(fullchain_pem)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(
description="Renew ACME certificates for a list of domains. This script assumes you already have an account on the CA server and a private key for each certificate.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("domains", nargs='+',
help="List of domain names for which to renew the certificate.")
parser.add_argument("--account_key", "-a",
default=os.path.join(SSL_CONFIG_DIR, "accounts", "acme_account.key"),
help="Path to the account key.")
parser.add_argument("--privkey", "-p",
default=os.path.join(SSL_CONFIG_DIR, "private", "{domain}.pem"),
help="Path to the private certificate.")
parser.add_argument("--csr", "-r",
default=os.path.join(SSL_CONFIG_DIR, "csr", "{domain}.pem"),
help="Path to the CSR file. If the file doesn't exist, it will be generated from the private key and the domain name.")
parser.add_argument("--certs", "-o",
default=os.path.join(SSL_CONFIG_DIR, "certs", "{domain}.d"),
help="Path to the certificates directory.")
parser.add_argument("--challenge", "-c",
default="/var/www/html/.well-known/acme-challenge",
help="Path to the challenge directory.")
parser.add_argument("--directory_url", "-u",
default="https://acme-v02.api.letsencrypt.org/directory",
help="Directory URL on which performing ACME challenges. Only ACME v2 is supported.")
parser.add_argument("--days", "-d", type=int,
default=30,
help="Days before attempting to renew the certificates.")
parser.add_argument("--quiet", "-q",
action="store_true",
help="Quiet mode.")
parser.add_argument("--force", "-f",
action="store_true",
help="Force certificates renewal without checking their expiration date.")
args = parser.parse_args()
log_level = logging.WARN if args.quiet else logging.INFO
logging.basicConfig(stream=sys.stdout, level=log_level, format="%(levelname)s:%(message)s")
for domain in args.domains:
account_key = args.account_key.format(domain=domain)
privkey = args.privkey.format(domain=domain)
csr = args.csr.format(domain=domain)
certs_dir = args.certs.format(domain=domain)
challenge_dir = args.challenge.format(domain=domain)
renew_cert(domain,
account_key_path=account_key,
privkey_path=privkey,
csr_path=csr,
certs_dir=certs_dir,
challenge_dir=challenge_dir,
directory_url=args.directory_url,
days_before_renewal=args.days,
force=args.force)

View File

@ -0,0 +1,76 @@
- name: Install ACME dependencies
apt:
name: python3-acme
state: present
tags: acme_install
- name: Create Let's Encrypt config directories
file:
path: "{{ config_dir }}"
state: directory
owner: root
group: "{{ acme_ssl_group }}"
mode: "711"
loop:
- "{{ acme_config_dir }}"
- "{{ acme_keys_dir }}"
- "{{ acme_accounts_dir }}"
- "{{ acme_csr_dir }}"
loop_control:
loop_var: config_dir
tags: acme_install
- name: Create challenge directory
file:
path: "{{ acme_challenge_dir }}/.well-known/acme-challenge"
state: directory
owner: root
group: root
mode: "755"
tags: acme_install
- name: Perform ACME challenge for each domain
include_tasks:
file: challenge.yml
apply:
tags: acme_challenge
loop: "{{ acme_domains | unique }}"
loop_control:
loop_var: domain_name
tags: acme_challenge
- name: Create directory for certificate renewal tool
file:
path: /opt/acme
owner: root
group: root
mode: "755"
state: directory
tags: acme_renew
- name: Copy script to renew ACME certificates
copy:
src: acme_renew_cert.py
dest: /opt/acme/acme_renew_cert.py
owner: root
group: root
mode: "755"
tags: acme_renew
- name: Setup cron job for ACME certificates renewal of {{ domain_name }}
cron:
name: acme renew {{ domain_name }} cert
job: >-
sleep $((RANDOM % 3600)) && /opt/acme/acme_renew_cert.py {{ domain_name }} -q
-a {{ (acme_accounts_dir + '/' + acme_account_key) | quote }}
-p {{ acme_keys_dir | quote }}/{domain}.pem
-r {{ acme_csr_dir | quote }}/{domain}.csr
-o {{ acme_certs_dir | quote }}/{domain}.d
-c {{ acme_challenge_dir | quote }}/.well-known/acme-challenge
minute: "30"
hour: "2"
state: present
loop: "{{ acme_domains | unique }}"
loop_control:
loop_var: domain_name
tags: acme_renew

View File

@ -0,0 +1,91 @@
- name: Create {{ domain_name }} certificates directory
file:
path: "{{ acme_certs_dir }}/{{ domain_name }}.d"
state: directory
owner: root
group: "{{ acme_ssl_group }}"
mode: "755"
tags: acme_install
- name: Generate Let's Encrypt account key
openssl_privatekey:
path: "{{ acme_accounts_dir }}/{{ acme_account_key }}"
owner: root
group: root
mode: "600"
type: RSA
size: 4096
tags: acme_account
- name: Generate Let's Encrypt private key for {{ domain_name }}
openssl_privatekey:
path: "{{ acme_keys_dir }}/{{ domain_name }}.pem"
owner: root
group: "{{ acme_ssl_group }}"
mode: "640"
type: RSA
size: 4096
- name: Generate Let's Encrypt CSR for {{ domain_name }}
openssl_csr:
path: "{{ acme_csr_dir }}/{{ domain_name }}.csr"
owner: root
group: "{{ acme_ssl_group }}"
mode: "644"
privatekey_path: "{{ acme_keys_dir }}/{{ domain_name }}.pem"
common_name: "{{ domain_name }}"
# - name: Check if Let's Encrypt certificate already exists for {{ domain_name }}
# stat:
# path: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem"
# register: _acme_cert_file
# - name: Check Let's Encrypt certificate expiration date for {{ domain_name }}
# openssl_certificate_info:
# path: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem"
# valid_at:
# thirty_days: "+30d"
# register: _acme_cert_validity
# when: _acme_cert_file.stat.isreg is defined and _acme_cert_file.stat.isreg
- name: Begin Let's Encrypt challenges for {{ domain_name }}
acme_certificate:
acme_directory: "{{ acme_directory }}"
acme_version: "{{ acme_version }}"
account_key_src: "{{ acme_accounts_dir }}/{{ acme_account_key }}"
account_email: "{{ acme_email }}"
terms_agreed: yes
challenge: http-01
csr: "{{ acme_csr_dir }}/{{ domain_name }}.csr"
dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem"
fullchain_dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/fullchain.pem"
remaining_days: 30
register: _acme_challenge
# when: _acme_cert_validity is skipped or not _acme_cert_validity.valid_at.thirty_days
- debug:
var: _acme_challenge
# - name: Implement and complete Let's Encrypt challenge for {{ domain_name }}
# when: _acme_challenge is not skipped
# block:
# - name: Implement http-01 challenge files for {{ domain_name }}
# copy:
# content: "{{ _acme_challenge.challenge_data[domain_name]['http-01'].resource_value }}"
# dest: "{{ acme_challenge_dir }}/{{ _acme_challenge.challenge_data[domain_name]['http-01'].resource }}"
# owner: root
# group: root
# mode: "644"
# - name: Complete Let's Encrypt challenges for {{ domain_name }}
# acme_certificate:
# acme_directory: "{{ acme_directory }}"
# acme_version: "{{ acme_version }}"
# account_key_src: "{{ acme_accounts_dir }}/{{ acme_account_key }}"
# account_email: "{{ acme_email }}"
# challenge: http-01
# csr: "{{ acme_csr_dir }}/{{ domain_name }}.csr"
# dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem"
# chain_dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/chain.pem"
# fullchain_dest: "{{ acme_certs_dir }}/{{ domain_name }}.d/fullchain.pem"
# data: "{{ _acme_challenge }}"

View File

@ -0,0 +1,2 @@
- import_tasks: acme.yml
tags: acme

View File

@ -0,0 +1 @@
acme_version: 2

View File

@ -0,0 +1,38 @@
Role Name
=========
A brief description of the role goes here.
Requirements
------------
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
Role Variables
--------------
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
Dependencies
------------
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
License
-------
BSD
Author Information
------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed).

View File

@ -0,0 +1,11 @@
---
# defaults file for common
node_exporter_path: /
node_exporter_port: 9100
node_exporter_public_port: "9180"
nginx_config_dir: /etc/nginx/conf.d
nginx_ssl_dir: /etc/nginx/ssl
ssh_config_dir: ~/.ssh
backup_targets: []

View File

@ -0,0 +1,8 @@
- name: restart openssh
service:
name: ssh
state: restarted
- name: reload nginx
include_tasks: ../handlers/nginx.yml
when: nginx_started is not changed

View File

@ -0,0 +1,7 @@
- name: Validate Nginx config
command: nginx -t
- name: Reload Nginx server
service:
name: nginx
state: reloaded

View File

@ -0,0 +1,16 @@
- import_tasks: repos.yml
tags: repos
- import_tasks: users.yml
tags: users
- import_tasks: openssh.yml
tags: openssh
- import_tasks: ufw.yml
tags: firewall
- import_tasks: msmtp.yml
tags: smtp
- import_tasks: nginx.yml
tags: nginx
- import_tasks: node_exporter.yml
tags: node_exporter
#- import_tasks: backup.yml
# tags: backup

View File

@ -0,0 +1,18 @@
---
# Install and configure SMTP relay
- name: Install msmtp
apt:
name:
- msmtp
- msmtp-mta
state: present
- name: Copy msmtp configuration
template:
src: msmtp/msmtprc.j2
dest: /etc/msmtprc
- name: Copy aliases
template:
src: msmtp/aliases.j2
dest: /etc/aliases

View File

@ -0,0 +1,101 @@
---
# Install and configure Nginx
- name: Install htpasswd dependencies
apt:
name: python-passlib
state: present
- name: Install SSL dependencies
apt:
name: ssl-cert
state: present
- name: Install Nginx
apt:
name: nginx-full
state: present
- name: Create Nginx configuration directories
file:
path: "{{ config_dir }}"
state: directory
owner: root
group: www-data
mode: "755"
loop:
- "{{ nginx_config_dir }}"
- "{{ nginx_ssl_dir }}"
loop_control:
loop_var: config_dir
- name: Generate Diffie-Hellman parameters
# This can take a long time... So we are doing it in async mode
openssl_dhparam:
path: "{{ nginx_ssl_dir }}/dhparam.pem"
size: 3072
owner: root
group: www-data
async: 3600
poll: 0
register: nginx_dh
- name: Use snakoil cert key as Nginx's default private key
file:
src: "/etc/ssl/private/ssl-cert-snakeoil.key"
path: "{{ nginx_ssl_dir }}/nginx.key"
state: link
owner: root
group: www-data
mode: "750"
force: yes
- name: Use snakoil cert as Nginx's default certificate
file:
src: "/etc/ssl/certs/ssl-cert-snakeoil.pem"
path: "{{ nginx_ssl_dir }}/nginx.crt"
state: link
owner: root
group: www-data
mode: "755"
force: yes
- name: Copy default Nginx config
template:
src: nginx/default.conf.j2
dest: /etc/nginx/sites-available/default
owner: root
group: www-data
mode: "755"
notify: reload nginx
- name: Enable default Nginx config
file:
src: /etc/nginx/sites-available/default
dest: /etc/nginx/sites-enabled/default
owner: root
group: www-data
mode: "755"
notify: reload nginx
- name: Allow default Nginx ports
ufw:
rule: allow
name: "Nginx Full"
- name: Waiting for Diffie-Hellman task to complete…
async_status:
jid: "{{ nginx_dh.ansible_job_id }}"
register: nginx_dh_job
retries: 60
delay: 30 # will retry every 30s for 30min (60 retries)
until: nginx_dh_job.finished
- name: Start Nginx server
service:
name: nginx
state: started
enabled: yes
register: nginx_started
- name: "Trigger Nginx handlers"
meta: flush_handlers

View File

@ -0,0 +1,30 @@
---
# Install and configure node-exporter
- name: Include role for installing node-exporter
include_role:
name: cloudalchemy.node-exporter
public: yes
vars:
node_exporter_web_listen_address: "0.0.0.0:{{ node_exporter_port }}"
- name: Configure Nginx for node-exporter
import_role:
name: nginx
vars:
nginx_config_file: node-exporter.conf
nginx_server:
name: "{{ inventory_hostname }}"
port: "{{ node_exporter_public_port }}"
locations:
- path: "{{ node_exporter_path }}"
basic_auth:
file: .htpasswd.node-exporter
password: "{{ node_exporter_password }}"
proxy_pass:
port: "{{ node_exporter_port }}"
path: /metrics
- name: Allow node-exporter port {{ node_exporter_public_port }}
ufw:
rule: allow
port: "{{ node_exporter_public_port }}"

View File

@ -0,0 +1,19 @@
---
# Configure OpenSSH server
- name: Configure OpenSSH server
template:
src: openssh/sshd_config.j2
dest: /etc/ssh/sshd_config
backup: yes
owner: "0"
group: "0"
mode: "0644"
validate: '/usr/sbin/sshd -T -f %s'
notify: restart openssh
- name: Trigger Ansible handlers
meta: flush_handlers
- name: Change Ansible SSH port to {{ openssh_port }}
set_fact:
ansible_port: "{{ openssh_port }}"

View File

@ -0,0 +1,12 @@
---
# Configure APT repositories and automatic upgrades
- name: Safely upgrade the server
apt:
upgrade: safe
update_cache: yes
cache_valid_time: "3600"
- name: Install unattend-upgrades for automatic upgrades
apt:
name: unattended-upgrades
state: present

View File

@ -0,0 +1,15 @@
---
# Install and configure UFW, the uncomplicated firewall
- name: Install UFW, the uncomplicated firewall
apt:
name: ufw
state: present
- name: Allow OpenSSH port {{ openssh_port }}
ufw:
rule: allow
port: "{{ openssh_port }}"
- name: Enable UFW config
ufw:
state: enabled

View File

@ -0,0 +1,22 @@
---
# Create an user and add their SSH public keys
- name: Create user {{ user.name }} with no password
user:
name: "{{ user.name }}"
shell: /bin/bash
# See https://unix.stackexchange.com/questions/193066/how-to-unlock-account-for-public-key-ssh-authorization-but-not-for-password-aut/193131#193131
password: '*'
groups:
- sudo
append: yes
state: present
update_password: on_create
- name: Add SSH public keys for user {{ user.name }}
authorized_key:
user: "{{ user.name }}"
state: present
# we can pass multiple SSH keys, but they must be separated by newlines
key: "{{ user.ssh_keys | join('\n') }}"
# remove obsolete keys
exclusive: yes

View File

@ -0,0 +1,28 @@
---
# Create users and add their SSH public keys
- name: Install sudo package
package:
name: sudo
state: present
tags: sudo
- name: Remove password to become root with sudo
lineinfile:
path: /etc/sudoers
state: present
regexp: '^%sudo'
line: '%sudo ALL=(ALL) NOPASSWD: ALL'
validate: 'visudo -cf %s'
tags: sudo
- name: Remove password for root user
user:
name: root
shell: /bin/bash
state: present
- name: Create users and add their SSH public keys
include_tasks: user.yml
loop: "{{ users }}"
loop_control:
loop_var: user

View File

@ -0,0 +1 @@
default: {{ smtp_default_contact }}

View File

@ -0,0 +1,19 @@
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile /var/log/msmtp.log
{% for account in smtp_accounts %}
account {{ account.name }}
host {{ account.host }}
port 587
from {{ account.from }}
user {{ account.user | default(account.from) }}
password {{ account.password }}
{% endfor %}
account default : {{ smtp_default_account }}
aliases /etc/aliases

View File

@ -0,0 +1,62 @@
{{ ansible_managed | comment }}
# Default server configuration
#
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /var/www/default;
server_name _;
location ^~ /.well-known/acme-challenge/ {
allow all;
root /var/www/acme;
try_files $uri =404;
}
location / {
try_files $uri $uri/ =404;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~* ^.+.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js)$ {
expires 7d;
log_not_found off;
access_log off;
}
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
server {
listen 443 default_server;
listen [::]:443 default_server;
server_name _;
include snippets/snakeoil.conf;
return 444;
}

View File

@ -0,0 +1,31 @@
{{ ansible_managed | comment }}
{% if nginx_server_name is defined %}
server {
listen {{ nginx_port }};
server_name {{ nginx_server_name }};
{% endif %}
{% for location in nginx_locations %}
location {{ location }} {
{% if location.proxy_pass is defined %}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
{% endif %}
{% if location.basic_auth_file is defined %}
auth_basic "Authentication required";
auth_basic_user_file /etc/nginx/{{ location.basic_auth_file }};
{% endif %}
{% if location.proxy_pass is defined %}
proxy_pass http://localhost:{{ location.proxy_pass.port | default('80') }}{{ location.proxy_pass.path }};
{% endif %}
}
{% endif %}
{% if nginx_server_name is defined %}
}
{% endif %}

View File

@ -0,0 +1,17 @@
# {{ ansible_managed }}
Port {{ openssh_port }}
PermitRootLogin no
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding yes
PrintMotd no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server

View File

@ -0,0 +1,2 @@
---
# vars file for common

View File

@ -0,0 +1,38 @@
Role Name
=========
A brief description of the role goes here.
Requirements
------------
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
Role Variables
--------------
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
Dependencies
------------
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
License
-------
BSD
Author Information
------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed).

View File

@ -0,0 +1,2 @@
---
# defaults file for roles/docker

View File

@ -0,0 +1,2 @@
---
# handlers file for roles/docker

View File

@ -0,0 +1,53 @@
galaxy_info:
author: your name
description: your description
company: your company (optional)
# If the issue tracker for your role is not on github, uncomment the
# next line and provide a value
# issue_tracker_url: http://example.com/issue/tracker
# Choose a valid license ID from https://spdx.org - some suggested licenses:
# - BSD-3-Clause (default)
# - MIT
# - GPL-2.0-or-later
# - GPL-3.0-only
# - Apache-2.0
# - CC-BY-4.0
license: license (GPL-2.0-or-later, MIT, etc)
min_ansible_version: 2.4
# If this a Container Enabled role, provide the minimum Ansible Container version.
# min_ansible_container_version:
#
# Provide a list of supported platforms, and for each platform a list of versions.
# If you don't wish to enumerate all versions for a particular platform, use 'all'.
# To view available platforms and versions (or releases), visit:
# https://galaxy.ansible.com/api/v1/platforms/
#
# platforms:
# - name: Fedora
# versions:
# - all
# - 25
# - name: SomePlatform
# versions:
# - all
# - 1.0
# - 7
# - 99.99
galaxy_tags: []
# List tags for your role here, one per line. A tag is a keyword that describes
# and categorizes the role. Users find roles by searching for tags. Be sure to
# remove the '[]' above, if you add tags to this list.
#
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
# Maximum 20 tags per role.
dependencies: []
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.

View File

@ -0,0 +1,45 @@
---
# tasks file for roles/docker
- name: install required system packages
apt:
names:
- apt-transport-https
- 'ca-certificates'
- 'curl'
- 'software-properties-common'
- 'python3-pip'
- 'virtualenv'
- 'python3-setuptools'
state: present
- name: add Docker GPG apt Key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: add Docker Repository
apt_repository:
repo: deb https://download.docker.com/linux/ubuntu bionic stable
state: present
register: docker_repo
- name: update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
when: docker_repo is changed
- name: install docker-ce package
apt:
name: docker-ce
state: present
- name: enable docker service
systemd:
name: docker
state: started
enabled: yes
- name: install Docker Module for Python
pip:
name: docker

View File

@ -0,0 +1,2 @@
localhost

View File

@ -0,0 +1,5 @@
---
- hosts: localhost
remote_user: root
roles:
- roles/docker

View File

@ -0,0 +1,2 @@
---
# vars file for roles/docker

View File

@ -0,0 +1,17 @@
---
# defaults file for roles/prometheus
grafana_admin_user: admin
grafana_domain: "{{ inventory_hostname }}"
grafana_web_path: /grafana
grafana_protocol: http
grafana_port: 3000
prometheus_domain: "{{ inventory_hostname }}"
prometheus_web_path: /prometheus
prometheus_port: 9090
nginx_default_path: "{{ grafana_web_path }}"
alertmanager_domain: "{{ inventory_hostname }}"
alertmanager_web_path: /alertmanager
alertmanager_port: 9093

View File

@ -0,0 +1,43 @@
groups:
- name: Hardware alerts
rules:
- alert: Instancedown
expr: up == 0
for: 5m
labels:
severity: warning
annotations:
title: Instance {{ $labels.instance }} is down
description: Failed to scrape {{ $labels.job }} on {{ $labels.instance }} for more than 5 minute. Instance seems down.
- alert: LowRootDiskSpace
expr: (node_filesystem_free_bytes{device =~ "/dev/.+"} / node_filesystem_size_bytes{device =~ "/dev/.+"} * 100) < 10
for: 5m
labels:
severity: warning
annotations:
title: Low free root space on {{ $labels.instance }}
description: On {{ $labels.instance }} device {{ $labels.device }} mounted on {{ $labels.mountpoint }} has low free space of {{ $value }}%
- alert: LowDataDiskSpace
expr: (node_filesystem_free_bytes{device !~ "/dev/.+", fstype !~ "tmpfs|.*lxcfs"} / node_filesystem_size_bytes{device !~ "/dev/.+", fstype !~ "tmpfs|.*lxcfs"} * 100) < 10
for: 5m
labels:
severity: warning
annotations:
title: Low free data space on {{ $labels.instance }}
description: On {{ $labels.instance }} device {{ $labels.device }} mounted on {{ $labels.mountpoint }} has low free space of {{ $value }}%
- alert: HighCPULoad
expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 5m
labels:
severity: warning
annotations:
title: High CPU load on instance {{ $labels.instance }}
description: Instance {{ $labels.instance }} has high CPU load.
- alert: HighMemoryUsage
expr: (1 - ((node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes) / node_memory_MemTotal_bytes)) * 100 > 90
for: 5m
labels:
severity: warning
annotations:
description: Instance {{ $labels.instance }} has high memory usage
summary: High memory usage on {{ $labels.instance }}

View File

@ -0,0 +1,13 @@
---
# handlers file for roles/prometheus
- name: restart grafana
service:
name: grafana-server
state: restarted
when: not grafana_service.changed
- name: restart prometheus
service:
name: prometheus
state: restarted
when: not prometheus_service.changed

View File

@ -0,0 +1,24 @@
- name: Install Alertmanager
import_role:
name: cloudalchemy.alertmanager
public: yes
vars:
alertmanager_web_external_url: "http://{{ alertmanager_domain }}{{ alertmanager_web_path }}"
alertmanager_config_flags_extra:
web.route-prefix: /
alertmanager_web_listen_address: "0.0.0.0:{{ alertmanager_port }}"
- name: Configure Nginx for Alertmaneger
import_role:
name: nginx
vars:
nginx_config_file: "{{ inventory_hostname }}.d/alertmanager.conf"
nginx_server:
locations:
- path: "{{ alertmanager_web_path }}/"
basic_auth:
file: .htpasswd.alertmanager
password: "{{ alertmanager_password }}"
proxy_pass:
port: "{{ alertmanager_port }}"
path: /

View File

@ -0,0 +1,71 @@
- name: add grafana GPG apt key
apt_key:
url: https://packages.grafana.com/gpg.key
state: present
- block:
- name: add grafana repository
apt_repository:
repo: deb https://packages.grafana.com/oss/deb stable main
state: present
register: grafana_repo
notify: restart grafana
- name: update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
when: grafana_repo is changed
- name: install grafana package
apt:
pkg: grafana
state: present
notify: restart grafana
- name: copy grafana config
template:
src: grafana.ini.j2
dest: /etc/grafana/grafana.ini
become: yes
notify: restart grafana
- name: enable grafana service
systemd:
name: grafana-server
state: started
enabled: yes
register: grafana_service
- name: Configure Nginx for Grafana
import_role:
name: nginx
vars:
nginx_config_file: "{{ inventory_hostname }}.conf"
nginx_server:
name: "{{ inventory_hostname }}"
port: 80
locations:
- path: "= /"
return:
code: 301
url: "http://{{ inventory_hostname }}{{ grafana_web_path }}"
includes:
- "{{ nginx_config_dir }}/{{ inventory_hostname }}.d/*.conf"
- name: ensure nginx config directory exists
file:
path: "{{ nginx_config_dir }}/{{ inventory_hostname }}.d"
state: directory
- include_role:
name: nginx
tasks_from: configure
vars:
nginx_config_file: "{{ inventory_hostname }}.d/grafana.conf"
nginx_server:
locations:
- path: "{{ grafana_web_path }}/"
proxy_pass:
port: "{{ grafana_port }}"
path: /

View File

@ -0,0 +1,8 @@
---
- import_tasks: grafana.yml
become: yes
tags: grafana
- import_tasks: alertmanager.yml
tags: alertmanager
- import_tasks: prometheus.yml
tags: prometheus

View File

@ -0,0 +1,26 @@
- name: Install Prometheus
import_role:
name: cloudalchemy.prometheus
public: yes
vars:
prometheus_config_file: "prometheus_custom.yml.j2"
prometheus_alert_rules: []
prometheus_web_external_url: "http://{{ prometheus_domain }}{{ prometheus_web_path }}"
prometheus_config_flags_extra:
web.route-prefix: /
prometheus_web_listen_address: "0.0.0.0:{{ prometheus_port }}"
- name: Configure Nginx for Prometheus
import_role:
name: nginx
vars:
nginx_config_file: "{{ inventory_hostname }}.d/prometheus.conf"
nginx_server:
locations:
- path: "{{ prometheus_web_path }}/"
basic_auth:
file: .htpasswd.prometheus
password: "{{ prometheus_password }}"
proxy_pass:
port: "{{ prometheus_port }}"
path: /

View File

@ -0,0 +1,627 @@
[server]
[security]
##################### Grafana Configuration Example #####################
#
# Everything has defaults so you only need to uncomment things you want to
# change
# possible values : production, development
;app_mode = production
# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
;instance_name = ${HOSTNAME}
#################################### Paths ####################################
[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
;data = /var/lib/grafana
# Temporary files in `data` directory older than given duration will be removed
;temp_data_lifetime = 24h
# Directory where grafana can store logs
;logs = /var/log/grafana
# Directory where grafana will automatically scan and look for plugins
;plugins = /var/lib/grafana/plugins
# folder that contains provisioning config files that grafana will apply on startup and while running.
;provisioning = conf/provisioning
#################################### Server ####################################
[server]
# Protocol (http, https, h2, socket)
protocol = {{ grafana_protocol }}
# The ip address to bind to, empty will bind to all interfaces
;http_addr =
# The http port to use
http_port = {{ grafana_port }}
# The public facing domain name used to access grafana from a browser
domain = {{ grafana_domain }}
# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
;enforce_domain = false
# The full public facing url you use in browser, used for redirects and emails
# If you use reverse proxy and sub path specify full url (with sub path)
root_url = {{ grafana_protocol }}://{{ grafana_domain }}{{ grafana_web_path }}
# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons.
;serve_from_sub_path = false
# Log web requests
;router_logging = false
# the path relative working path
;static_root_path = public
# enable gzip
;enable_gzip = false
# https certs & key file
;cert_file =
;cert_key =
# Unix socket path
;socket =
#################################### Database ####################################
[database]
# You can configure the database connection by specifying type, host, name, user and password
# as separate properties or as on string using the url properties.
# Either "mysql", "postgres" or "sqlite3", it's your choice
;type = sqlite3
;host = 127.0.0.1:3306
;name = grafana
;user = root
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
;password =
# Use either URL or the previous fields to configure the database
# Example: mysql://user:secret@host:port/database
;url =
# For "postgres" only, either "disable", "require" or "verify-full"
;ssl_mode = disable
# For "sqlite3" only, path relative to data_path setting
;path = grafana.db
# Max idle conn setting default is 2
;max_idle_conn = 2
# Max conn setting default is 0 (mean not set)
;max_open_conn =
# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
;conn_max_lifetime = 14400
# Set to true to log the sql calls and execution times.
;log_queries =
# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
;cache_mode = private
#################################### Cache server #############################
[remote_cache]
# Either "redis", "memcached" or "database" default is "database"
;type = database
# cache connectionstring options
# database: will use Grafana primary database.
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'.
# memcache: 127.0.0.1:11211
;connstr =
#################################### Data proxy ###########################
[dataproxy]
# This enables data proxy logging, default is false
;logging = false
# How long the data proxy should wait before timing out default is 30 (seconds)
;timeout = 30
# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false.
;send_user_header = false
#################################### Analytics ####################################
[analytics]
# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
# No ip addresses are being tracked, only simple counters to track
# running instances, dashboard and error counts. It is very helpful to us.
# Change this option to false to disable reporting.
;reporting_enabled = true
# Set to false to disable all checks to https://grafana.net
# for new vesions (grafana itself and plugins), check is used
# in some UI views to notify that grafana or plugin update exists
# This option does not cause any auto updates, nor send any information
# only a GET request to http://grafana.com to get latest versions
;check_for_updates = true
# Google Analytics universal tracking code, only enabled if you specify an id here
;google_analytics_ua_id =
# Google Tag Manager ID, only enabled if you specify an id here
;google_tag_manager_id =
#################################### Security ####################################
[security]
# disable creation of admin user on first start of grafana
;disable_initial_admin_creation = false
# default admin user, created on startup
admin_user = {{ grafana_admin_user }}
# default admin password, can be changed before first start of grafana, or in profile settings
admin_password = {{ grafana_admin_password }}
# used for signing
;secret_key = SW2YcwTIb9zpOOhoPsMm
# disable gravatar profile images
;disable_gravatar = false
# data source proxy whitelist (ip_or_domain:port separated by spaces)
;data_source_proxy_whitelist =
# disable protection against brute force login attempts
;disable_brute_force_login_protection = false
# set to true if you host Grafana behind HTTPS. default is false.
;cookie_secure = false
# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none"
;cookie_samesite = lax
# set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false.
;allow_embedding = false
# Set to true if you want to enable http strict transport security (HSTS) response header.
# This is only sent when HTTPS is enabled in this configuration.
# HSTS tells browsers that the site should only be accessed using HTTPS.
# The default version will change to true in the next minor release, 6.3.
;strict_transport_security = false
# Sets how long a browser should cache HSTS. Only applied if strict_transport_security is enabled.
;strict_transport_security_max_age_seconds = 86400
# Set to true if to enable HSTS preloading option. Only applied if strict_transport_security is enabled.
;strict_transport_security_preload = false
# Set to true if to enable the HSTS includeSubDomains option. Only applied if strict_transport_security is enabled.
;strict_transport_security_subdomains = false
# Set to true to enable the X-Content-Type-Options response header.
# The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types advertised
# in the Content-Type headers should not be changed and be followed. The default will change to true in the next minor release, 6.3.
;x_content_type_options = false
# Set to true to enable the X-XSS-Protection header, which tells browsers to stop pages from loading
# when they detect reflected cross-site scripting (XSS) attacks. The default will change to true in the next minor release, 6.3.
;x_xss_protection = false
#################################### Snapshots ###########################
[snapshots]
# snapshot sharing options
;external_enabled = true
;external_snapshot_url = https://snapshots-origin.raintank.io
;external_snapshot_name = Publish to snapshot.raintank.io
# Set to true to enable this Grafana instance act as an external snapshot server and allow unauthenticated requests for
# creating and deleting snapshots.
;public_mode = false
# remove expired snapshot
;snapshot_remove_expired = true
#################################### Dashboards History ##################
[dashboards]
# Number dashboard versions to keep (per dashboard). Default: 20, Minimum: 1
;versions_to_keep = 20
#################################### Users ###############################
[users]
# disable user signup / registration
;allow_sign_up = true
# Allow non admin users to create organizations
;allow_org_create = true
# Set to true to automatically assign new users to the default organization (id 1)
;auto_assign_org = true
# Default role new users will be automatically assigned (if disabled above is set to true)
;auto_assign_org_role = Viewer
# Background text for the user field on the login page
;login_hint = email or username
;password_hint = password
# Default UI theme ("dark" or "light")
;default_theme = dark
# External user management, these options affect the organization users view
;external_manage_link_url =
;external_manage_link_name =
;external_manage_info =
# Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard.
;viewers_can_edit = false
# Editors can administrate dashboard, folders and teams they create
;editors_can_admin = false
[auth]
# Login cookie name
;login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days,
;login_maximum_inactive_lifetime_days = 7
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
;login_maximum_lifetime_days = 30
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
;token_rotation_interval_minutes = 10
# Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false
;disable_login_form = false
# Set to true to disable the signout link in the side menu. useful if you use auth.proxy, defaults to false
;disable_signout_menu = false
# URL to redirect the user to after sign out
;signout_redirect_url =
# Set to true to attempt login with OAuth automatically, skipping the login screen.
# This setting is ignored if multiple OAuth providers are configured.
;oauth_auto_login = false
#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
;enabled = false
# specify organization name that should be used for unauthenticated users
;org_name = Main Org.
# specify role for unauthenticated users
;org_role = Viewer
#################################### Github Auth ##########################
[auth.github]
;enabled = false
;allow_sign_up = true
;client_id = some_id
;client_secret = some_secret
;scopes = user:email,read:org
;auth_url = https://github.com/login/oauth/authorize
;token_url = https://github.com/login/oauth/access_token
;api_url = https://api.github.com/user
;team_ids =
;allowed_organizations =
#################################### Google Auth ##########################
[auth.google]
;enabled = false
;allow_sign_up = true
;client_id = some_client_id
;client_secret = some_client_secret
;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
;auth_url = https://accounts.google.com/o/oauth2/auth
;token_url = https://accounts.google.com/o/oauth2/token
;api_url = https://www.googleapis.com/oauth2/v1/userinfo
;allowed_domains =
#################################### Generic OAuth ##########################
[auth.generic_oauth]
;enabled = false
;name = OAuth
;allow_sign_up = true
;client_id = some_id
;client_secret = some_secret
;scopes = user:email,read:org
;email_attribute_name = email:primary
;email_attribute_path =
;auth_url = https://foo.bar/login/oauth/authorize
;token_url = https://foo.bar/login/oauth/access_token
;api_url = https://foo.bar/user
;team_ids =
;allowed_organizations =
;role_attribute_path =
;tls_skip_verify_insecure = false
;tls_client_cert =
;tls_client_key =
;tls_client_ca =
; Set to true to enable sending client_id and client_secret via POST body instead of Basic authentication HTTP header
; This might be required if the OAuth provider is not RFC6749 compliant, only supporting credentials passed via POST payload
;send_client_credentials_via_post = false
#################################### SAML Auth ###########################
[auth.saml] # Enterprise only
# Defaults to false. If true, the feature is enabled.
;enabled = false
# Base64-encoded public X.509 certificate. Used to sign requests to the IdP
;certificate =
# Path to the public X.509 certificate. Used to sign requests to the IdP
;certificate_path =
# Base64-encoded private key. Used to decrypt assertions from the IdP
;private_key =
;# Path to the private key. Used to decrypt assertions from the IdP
;private_key_path =
# Base64-encoded IdP SAML metadata XML. Used to verify and obtain binding locations from the IdP
;idp_metadata =
# Path to the SAML metadata XML. Used to verify and obtain binding locations from the IdP
;idp_metadata_path =
# URL to fetch SAML IdP metadata. Used to verify and obtain binding locations from the IdP
;idp_metadata_url =
# Duration, since the IdP issued a response and the SP is allowed to process it. Defaults to 90 seconds.
;max_issue_delay = 90s
# Duration, for how long the SP's metadata should be valid. Defaults to 48 hours.
;metadata_valid_duration = 48h
# Friendly name or name of the attribute within the SAML assertion to use as the user's name
;assertion_attribute_name = displayName
# Friendly name or name of the attribute within the SAML assertion to use as the user's login handle
;assertion_attribute_login = mail
# Friendly name or name of the attribute within the SAML assertion to use as the user's email
;assertion_attribute_email = mail
#################################### Grafana.com Auth ####################
[auth.grafana_com]
;enabled = false
;allow_sign_up = true
;client_id = some_id
;client_secret = some_secret
;scopes = user:email
;allowed_organizations =
#################################### Auth Proxy ##########################
[auth.proxy]
;enabled = false
;header_name = X-WEBAUTH-USER
;header_property = username
;auto_sign_up = true
;sync_ttl = 60
;whitelist = 192.168.1.1, 192.168.2.1
;headers = Email:X-User-Email, Name:X-User-Name
# Read the auth proxy docs for details on what the setting below enables
;enable_login_token = false
#################################### Basic Auth ##########################
[auth.basic]
;enabled = true
#################################### Auth LDAP ##########################
[auth.ldap]
;enabled = false
;config_file = /etc/grafana/ldap.toml
;allow_sign_up = true
# LDAP backround sync (Enterprise only)
# At 1 am every day
;sync_cron = "0 0 1 * * *"
;active_sync_enabled = true
#################################### SMTP / Emailing ##########################
[smtp]
;enabled = false
;host = localhost:25
;user =
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
;password =
;cert_file =
;key_file =
;skip_verify = false
;from_address = admin@grafana.localhost
;from_name = Grafana
# EHLO identity in SMTP dialog (defaults to instance_name)
;ehlo_identity = dashboard.example.com
[emails]
;welcome_email_on_sign_up = false
#################################### Logging ##########################
[log]
# Either "console", "file", "syslog". Default is console and file
# Use space to separate multiple modes, e.g. "console file"
;mode = console file
# Either "debug", "info", "warn", "error", "critical", default is "info"
;level = info
# optional settings to set different levels for specific loggers. Ex filters = sqlstore:debug
;filters =
# For "console" mode only
[log.console]
;level =
# log line format, valid options are text, console and json
;format = console
# For "file" mode only
[log.file]
;level =
# log line format, valid options are text, console and json
;format = text
# This enables automated log rotate(switch of following options), default is true
;log_rotate = true
# Max line number of single file, default is 1000000
;max_lines = 1000000
# Max size shift of single file, default is 28 means 1 << 28, 256MB
;max_size_shift = 28
# Segment log daily, default is true
;daily_rotate = true
# Expired days of log file(delete after max days), default is 7
;max_days = 7
[log.syslog]
;level =
# log line format, valid options are text, console and json
;format = text
# Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used.
;network =
;address =
# Syslog facility. user, daemon and local0 through local7 are valid.
;facility =
# Syslog tag. By default, the process' argv[0] is used.
;tag =
#################################### Alerting ############################
[alerting]
# Disable alerting engine & UI features
;enabled = true
# Makes it possible to turn off alert rule execution but alerting UI is visible
;execute_alerts = true
# Default setting for new alert rules. Defaults to categorize error and timeouts as alerting. (alerting, keep_state)
;error_or_timeout = alerting
# Default setting for how Grafana handles nodata or null values in alerting. (alerting, no_data, keep_state, ok)
;nodata_or_nullvalues = no_data
# Alert notifications can include images, but rendering many images at the same time can overload the server
# This limit will protect the server from render overloading and make sure notifications are sent out quickly
;concurrent_render_limit = 5
# Default setting for alert calculation timeout. Default value is 30
;evaluation_timeout_seconds = 30
# Default setting for alert notification timeout. Default value is 30
;notification_timeout_seconds = 30
# Default setting for max attempts to sending alert notifications. Default value is 3
;max_attempts = 3
#################################### Explore #############################
[explore]
# Enable the Explore section
;enabled = true
#################################### Internal Grafana Metrics ##########################
# Metrics available at HTTP API Url /metrics
[metrics]
# Disable / Enable internal metrics
;enabled = true
# Disable total stats (stat_totals_*) metrics to be generated
;disable_total_stats = false
# Publish interval
;interval_seconds = 10
# Send internal metrics to Graphite
[metrics.graphite]
# Enable by setting the address setting (ex localhost:2003)
;address =
;prefix = prod.grafana.%(instance_name)s.
#################################### Distributed tracing ############
[tracing.jaeger]
# Enable by setting the address sending traces to jaeger (ex localhost:6831)
;address = localhost:6831
# Tag that will always be included in when creating new spans. ex (tag1:value1,tag2:value2)
;always_included_tag = tag1:value1
# Type specifies the type of the sampler: const, probabilistic, rateLimiting, or remote
;sampler_type = const
# jaeger samplerconfig param
# for "const" sampler, 0 or 1 for always false/true respectively
# for "probabilistic" sampler, a probability between 0 and 1
# for "rateLimiting" sampler, the number of spans per second
# for "remote" sampler, param is the same as for "probabilistic"
# and indicates the initial sampling rate before the actual one
# is received from the mothership
;sampler_param = 1
# Whether or not to use Zipkin propagation (x-b3- HTTP headers).
;zipkin_propagation = false
# Setting this to true disables shared RPC spans.
# Not disabling is the most common setting when using Zipkin elsewhere in your infrastructure.
;disable_shared_zipkin_spans = false
#################################### Grafana.com integration ##########################
# Url used to import dashboards directly from Grafana.com
[grafana_com]
;url = https://grafana.com
#################################### External image storage ##########################
[external_image_storage]
# Used for uploading images to public servers so they can be included in slack/email messages.
# you can choose between (s3, webdav, gcs, azure_blob, local)
;provider =
[external_image_storage.s3]
;bucket =
;region =
;path =
;access_key =
;secret_key =
[external_image_storage.webdav]
;url =
;public_url =
;username =
;password =
[external_image_storage.gcs]
;key_file =
;bucket =
;path =
[external_image_storage.azure_blob]
;account_name =
;account_key =
;container_name =
[external_image_storage.local]
# does not require any configuration
[rendering]
# Options to configure a remote HTTP image rendering service, e.g. using https://github.com/grafana/grafana-image-renderer.
# URL to a remote HTTP image renderer service, e.g. http://localhost:8081/render, will enable Grafana to render panels and dashboards to PNG-images using HTTP requests to an external service.
;server_url =
# If the remote HTTP image renderer service runs on a different server than the Grafana server you may have to configure this to a URL where Grafana is reachable, e.g. http://grafana.domain/.
;callback_url =
[enterprise]
# Path to a valid Grafana Enterprise license.jwt file
;license_path =
[panels]
# If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities.
;disable_sanitize_html = false
[plugins]
;enable_alpha = false
;app_tls_skip_verify_insecure = false

View File

@ -0,0 +1,11 @@
location {{ alertmanager_web_path }}/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
auth_basic "Authentication required for Alert Manager";
auth_basic_user_file /etc/nginx/.htpasswd-alertmanager;
proxy_pass http://localhost:{{ alertmanager_port }}/;
}

View File

@ -0,0 +1,5 @@
{{ ansible_managed | comment }}
location {{ grafana_web_path }}/ {
proxy_pass http://localhost:{{ grafana_port }}/;
}

View File

@ -0,0 +1,11 @@
location {{ prometheus_web_path }}/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
auth_basic "Authentication required";
auth_basic_user_file /etc/nginx/.htpasswd-prometheus;
proxy_pass http://localhost:{{ prometheus_port }}/;
}

View File

@ -0,0 +1,60 @@
# Set the command-line arguments to pass to the server.
ARGS="--web.external-url={{ prometheus_protocol }}://{{ prometheus_domain }}{{ prometheus_path }} \
--web.route-prefix=/ \
--web.listen-address='0.0.0.0:{{ prometheus_port }}'"
# Prometheus supports the following options:
# --config.file="/etc/prometheus/prometheus.yml"
# Prometheus configuration file path.
# --web.listen-address="0.0.0.0:9090"
# Address to listen on for UI, API, and telemetry.
# --web.read-timeout=5m Maximum duration before timing out read of the
# request, and closing idle connections.
# --web.max-connections=512 Maximum number of simultaneous connections.
# --web.external-url=<URL> The URL under which Prometheus is externally
# reachable (for example, if Prometheus is served
# via a reverse proxy). Used for generating
# relative and absolute links back to Prometheus
# itself. If the URL has a path portion, it will
# be used to prefix all HTTP endpoints served by
# Prometheus. If omitted, relevant URL components
# will be derived automatically.
# --web.route-prefix=<path> Prefix for the internal routes of web endpoints.
# Defaults to path of --web.external-url.
# --web.local-assets="/usr/share/prometheus/web/"
# Path to static asset/templates directory.
# --web.user-assets=<path> Path to static asset directory, available at
# /user.
# --web.enable-lifecycle Enable shutdown and reload via HTTP request.
# --web.enable-admin-api Enables API endpoints for admin control actions.
# --web.console.templates="/etc/prometheus/consoles"
# Path to the console template directory,
# available at /consoles.
# --web.console.libraries="/etc/prometheus/console_libraries"
# Path to the console library directory.
# --storage.tsdb.path="/var/lib/prometheus/metrics2/"
# Base path for metrics storage.
# --storage.tsdb.min-block-duration=2h
# Minimum duration of a data block before being
# persisted.
# --storage.tsdb.max-block-duration=<duration>
# Maximum duration compacted blocks may span.
# (Defaults to 10% of the retention period)
# --storage.tsdb.retention=15d
# How long to retain samples in the storage.
# --storage.tsdb.no-lockfile
# Do not create lockfile in data directory.
# --alertmanager.notification-queue-capacity=10000
# The capacity of the queue for pending alert
# manager notifications.
# --alertmanager.timeout=10s
# Timeout for sending alerts to Alertmanager.
# --query.lookback-delta=5m The delta difference allowed for retrieving
# metrics during expression evaluations.
# --query.timeout=2m Maximum time a query may take before being
# aborted.
# --query.max-concurrency=20
# Maximum number of queries executed concurrently.
# --log.level=info Only log messages with the given severity or
# above. One of: [debug, info, warn, error]

View File

@ -0,0 +1,31 @@
global:
scrape_interval: 5s
scrape_timeout: 5s
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:{{ alertmanager_port }}
scheme: http
timeout: 10s
rule_files:
- '/etc/prometheus/rules.d/*.yml'
scrape_configs:
{% for host in groups['all'] %}
{% with
node_exporter_password = hostvars[host].node_exporter_password | default(node_exporter_password),
node_exporter_path = hostvars[host].node_exporter_path | default(node_exporter_path)
%}
- job_name: 'node-exporter-{{ host }}'
basic_auth:
username: admin
password: {{ node_exporter_password }}
metrics_path: {{ node_exporter_path }}
static_configs:
- targets:
- '{{ host }}'
{% endwith %}
{% endfor %}

View File

@ -0,0 +1,40 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
{{ ansible_managed | comment }}
# http://prometheus.io/docs/operating/configuration/
global:
scrape_interval: 5s
scrape_timeout: 5s
rule_files:
- {{ prometheus_config_dir }}/rules/*.rules
alerting:
alertmanagers:
- scheme: http
static_configs:
- targets:
- "localhost:{{ alertmanager_port }}"
timeout: 10s
{% if prometheus_alert_relabel_configs | length > 0 %}
alert_relabel_configs:
{{ prometheus_alert_relabel_configs | to_nice_yaml(indent=2) | indent(2,False) }}
{% endif %}
scrape_configs:
{% for host in groups['all'] %}
{% with
node_exporter_password = hostvars[host].node_exporter_password | default(node_exporter_password),
node_exporter_path = hostvars[host].node_exporter_path | default(node_exporter_path),
node_exporter_public_port = hostvars[host].node_exporter_public_port | default(node_exporter_public_port)
%}
- job_name: 'node-exporter-{{ host }}'
basic_auth:
username: admin
password: {{ node_exporter_password }}
metrics_path: {{ node_exporter_path }}
static_configs:
- targets:
- '{{ host }}:{{ node_exporter_public_port }}'
{% endwith %}
{% endfor %}

View File

@ -0,0 +1,24 @@
umurmur_user_password: ""
umurmur_channel_links:
- source: "{{ umurmur_default_channel }}"
destinations: >-
{{ umurmur_channels
| selectattr('parent', 'defined')
| selectattr('parent', '==', umurmur_default_channel)
| map(attribute='name')
| list
}}
umurmur_ssl_group: "{{ acme_ssl_group }}"
umurmur_certificate: "{{ acme_certs_dir }}/{{ umurmur_domain }}.d/fullchain.pem"
umurmur_private_key: "{{ acme_keys_dir }}/{{ umurmur_domain }}.pem"
umurmur_version: master
umurmur_ispublic: yes
umurmur_port: "64738"
mumble_web_certificate: "{{ acme_certs_dir }}/{{ mumble_web_domain }}.d/fullchain.pem"
mumble_web_trusted_certificate: "{{ acme_certs_dir }}/{{ mumble_web_domain }}.d/chain.pem"
mumble_web_private_key: "{{ acme_keys_dir }}/{{ mumble_web_domain }}.pem"
mumble_web_dhparam: "/etc/nginx/ssl/dhparam.pem"
mumble_web_www_dir: /var/www/mumble-web
mumble_web_version: master
mumble_web_websockify_port: "64737"

View File

@ -0,0 +1,21 @@
- name: reload systemd
systemd:
daemon_reload: yes
register: systemd_reloaded
- name: restart umurmur
service:
name: umurmur
state: restarted
when:
- not (umurmur_started.changed | default(false)) or (systemd_reloaded.changed | default(false))
- name: restart mumble-web
service:
name: mumble-web
state: restarted
when:
- not (mumble_web_started.changed | default(false)) or (systemd_reloaded.changed | default(false))
- name: reload nginx
include_tasks: ../handlers/nginx.yml

View File

@ -0,0 +1,7 @@
- name: Validate Nginx config
command: nginx -t
- name: Reload Nginx server
service:
name: nginx
state: reloaded

View File

@ -0,0 +1,5 @@
dependencies:
- role: geerlingguy.nodejs
vars:
nodejs_install_npm_user: root
tags: [nodejs,mumble_web]

View File

@ -0,0 +1,11 @@
- name: Install and configure umurmur server
import_tasks: umurmur.yml
tags: umurmur
- name: Install and configure mumble web client
import_tasks: mumble_web.yml
tags: mumble_web
- name: Configure Nginx for mumble web client
import_tasks: nginx.yml
tags: nginx

View File

@ -0,0 +1,77 @@
- name: Install websockify
apt:
name: websockify
state: present
notify: restart mumble-web
tags: mumble_web_install
- name: Clone mumble-web git repository
git:
repo: https://github.com/Johni0702/mumble-web.git
dest: "{{ mumble_web_www_dir }}"
version: "{{ mumble_web_version }}"
tags: mumble_web_install
- name: Change mumble-web git repository's permissions
file:
path: "{{ mumble_web_www_dir }}"
owner: root
group: www-data
mode: "755"
state: directory
tags: mumble_web_install
- name: Build mumble-web from sources
command: npm install
args:
chdir: "{{ mumble_web_www_dir }}"
register: _mumble_web_installed
changed_when: _mumble_web_installed.stdout is regex('(added|removed) [0-9]+ package')
tags: [mumble_web_install,mumble_web_build]
- name: Apply security suggestions
command: npm audit fix
args:
chdir: "{{ mumble_web_www_dir }}"
when: _mumble_web_installed is changed and _mumble_web_installed.stdout is search('npm audit fix')
tags: [mumble_web_install,mumble_web_build]
- name: Build mumble-web assets
command: npm run build
args:
chdir: "{{ mumble_web_www_dir }}"
register: _mumble_web_assets_built
when: _mumble_web_installed is changed
tags: [mumble_web_install,mumble_web_build]
- name: Copy mumble-web config file
template:
src: mumble-web.js.j2
dest: "{{ mumble_web_www_dir }}/dist/config.local.js"
owner: root
group: www-data
mode: "640"
tags: [mumble_web_install,mumble_web_config]
- name: Copy mumble-web systemd service
template:
src: mumble-web.service.j2
dest: /etc/systemd/system/mumble-web.service
owner: root
group: root
mode: "644"
notify:
- reload systemd
- restart mumble-web
tags: [mumble_web_install,mumble_web_config]
- name: Start mumble-web service
service:
name: mumble-web
state: started
enabled: yes
register: mumble_web_started
tags: mumble_web_run
- name: Trigger mumble-web handlers
meta: flush_handlers

View File

@ -0,0 +1,18 @@
- name: Copy Nginx config file
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/mumble.conf
owner: root
group: www-data
mode: "755"
notify: reload nginx
- name: Enable Nginx config file
file:
src: /etc/nginx/sites-available/mumble.conf
path: /etc/nginx/sites-enabled/mumble.conf
state: link
notify: reload nginx
- name: Trigger Nginx handlers
meta: flush_handlers

View File

@ -0,0 +1,88 @@
- name: Install umurmur build dependencies
apt:
name:
- git
- build-essential
- cmake
- libconfig-dev
- libprotobuf-c-dev
- libmbedtls-dev
- ssl-cert
- name: Clone umurmur git repository
git:
repo: https://github.com/umurmur/umurmur.git
dest: /opt/umurmur
version: "{{ umurmur_version }}"
- name: Change umurmur git repository's permissions
file:
path: /opt/umurmur
owner: root
group: root
mode: "775"
state: directory
- name: Create the build directory
file:
path: /opt/umurmur/build
owner: root
group: root
mode: "775"
state: directory
- name: Generate the Makefile with cmake
shell: cd /opt/umurmur/build && cmake .. -DSSL=mbedtls
changed_when: no
- name: Build umurmur from source
make:
chdir: /opt/umurmur/build
changed_when: _umurmur_built.stdout_lines | length > 1
register: _umurmur_built
- name: Install umurmur
make:
chdir: /opt/umurmur/build
target: install
changed_when: "'Installing' in _umurmur_installed.stdout"
register: _umurmur_installed
notify: restart umurmur
- name: Copy umurmur config file
template:
src: umurmur.conf.j2
dest: /usr/local/etc/umurmur.conf
owner: root
group: "{{ umurmur_ssl_group }}"
mode: "640"
validate: /usr/local/bin/umurmurd -t -c %s
notify: restart umurmur
tags: umurmur_config
- name: Copy umurmur systemd service
template:
src: umurmur.service.j2
dest: /etc/systemd/system/umurmur.service
owner: root
group: root
mode: "644"
notify:
- reload systemd
- restart umurmur
- name: Start umurmur service
service:
name: umurmur
enabled: yes
state: started
register: umurmur_started
- name: Trigger umurmur handlers
meta: flush_handlers
- name: Open umurmur port with UFW
ufw:
rule: allow
port: "{{ umurmur_port }}"
when: umurmur_ispublic | bool

View File

@ -0,0 +1,23 @@
{{ ansible_managed | comment('c') }}
// You can overwrite the default configuration values set in [config.js] here.
// There should never be any required changes to this file and you can always
// simply copy it over when updating to a new version.
let config = window.mumbleWebConfig // eslint-disable-line no-unused-vars
// E.g. changing default address and theme:
// config.defaults.address = 'voice.example.com'
// config.defaults.theme = 'MetroMumbleDark
// Which fields to show on the Connect to Server dialog
config.connectDialog.address = false
config.connectDialog.port = false
config.connectDialog.token = false
config.connectDialog.password = {{ (umurmur_user_password != '') | lower }}
// Default values for user settings
// You can see your current value by typing `localStorage.getItem('mumble.$setting')` in the web console.
config.settings.pttKey = 'shift'
// Default values (can be changed by passing a query parameter of the same name)
config.defaults.address = "{{ mumble_web_domain }}/mumble"

View File

@ -0,0 +1,26 @@
[Unit]
Description=Mumble web client using websockets
After=network.target
[Service]
Type=simple
User=nobody
Group=nogroup
Restart=on-failure
RestartSec=3
PIDFile=/run/mumble-web.pid
ExecStart=/usr/bin/websockify --ssl-target {{ mumble_web_websockify_port }} localhost:{{ umurmur_port }}
ExecReload=/bin/kill -HUP $MAINPID
PrivateDevices=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
LockPersonality=yes
NoNewPrivileges=yes
LimitRTPRIO=1
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,65 @@
{{ ansible_managed | comment }}
server {
listen 80;
listen [::]:80;
server_name {{ mumble_web_domain }};
location / {
return 301 https://$host$request_uri;
}
{% if acme_challenge_dir is defined %}
location ^~ /.well-known/acme-challenge/ {
allow all;
root {{ acme_challenge_dir }};
try_files $uri =404;
}
{% endif %}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name {{ mumble_web_domain }};
ssl_certificate {{ mumble_web_certificate }};
ssl_certificate_key {{ mumble_web_private_key }};
ssl_session_timeout 1d;
ssl_session_cache shared:AnsibleSSL:10m; # about 40000 sessions
ssl_session_tickets off;
{% if mumble_web_dhparam is defined and mumble_web_dhparam != '' %}
ssl_dhparam {{ mumble_web_dhparam }};
{% endif %}
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# Verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate {{ mumble_web_trusted_certificate }};
location / {
root /var/www/mumble-web/dist/;
}
location /mumble {
proxy_pass http://127.0.0.1:{{ mumble_web_websockify_port }};
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

View File

@ -0,0 +1,72 @@
{{ ansible_managed | comment }}
max_bandwidth = 48000;
welcometext = {{ umurmur_welcome_text
if umurmur_welcome_text is string
else (umurmur_welcome_text | join('<br />'))
| to_json }};
certificate = {{ umurmur_certificate | to_json }};
private_key = {{ umurmur_private_key | to_json }};
password = {{ umurmur_user_password | to_json }};
{% if umurmur_admin_password is defined %}
admin_password = {{ umurmur_admin_password | to_json }}; # Set to enable admin functionality.
{% endif %}
# ban_length = 0; # Length in seconds for a ban. Default is 0. 0 = forever.
# enable_ban = false; # Default is false
# banfile = "banfile.txt"; # File to save bans to. Default is to not save bans to file.
# sync_banfile = false; # Keep banfile synced. Default is false, which means it is saved to at shutdown only.
allow_textmessage = true; # Default is true
# opus_threshold = 100; # Percentage of users supporting Opus codec for it to be chosen. Default is 100.
# show_addresses = true; # Whether to show client's IP addresses under user information
max_users = {{ umurmur_max_users }};
bindport = {{ umurmur_port }};
# bindaddr = "0.0.0.0";
# username and groupname for privilege dropping.
# Will attempt to switch user if set.
# username = "";
# If groupname not set the user's default login group will be used
# groupname = "";
# Log to file option. Default is logging to syslog.
# umurmurd will close and reopen the logfile if SIGHUP is received.
logfile = "/var/log/umurmurd.log";
# CA location for CA-signed certificates
# ca_path = "/path/to/ca/certificates/";
# Channel tree definition:
# Root channel must always be defined first.
# If a channel has a parent, the parent must be defined before the child channel(s).
channels = (
{% for channel in umurmur_channels %}
{
name = {{ channel.name | to_json }};
description = {{ channel.description | default('') | to_json }};
parent = {{ channel.parent | default('') | to_json }};
noenter = {{ channel.noenter | default(false) | string | lower }};
silent = {{ channel.silent | default(false) | string | lower }};
{% if channel.position is defined %}
position = {{ channel.position }};
{% endif %}
{% if channel.password is defined %}
password = {{ channel.password | to_json }};
{% endif %}
}{{ loop.last | ternary("", ",") }}
{% endfor %}
);
# Channel links configuration.
channel_links = (
{% for channel in umurmur_channel_links %}
{% for destination in channel.destinations %}
{
source = {{ channel.source | to_json }};
destination = {{ destination | to_json }};
}{{ loop.last | ternary('', ',') }}
{% endfor %}
{% endfor %}
);
# The channel in which users will appear in when connecting.
# Note that default channel can't have 'noenter = true' or password set
default_channel = {{ umurmur_default_channel | to_json }};

View File

@ -0,0 +1,28 @@
[Unit]
Description=Minimalistic Mumble server
After=network.target
[Service]
Type=simple
User=nobody
Group={{ umurmur_ssl_group }}
Restart=on-failure
RestartSec=3
PIDFile=/run/umurmurd.pid
ExecStartPre=/usr/local/bin/umurmurd -t -c /usr/local/etc/umurmur.conf
ExecStart=/usr/local/bin/umurmurd -d -r -c /usr/local/etc/umurmur.conf
ExecReload=/bin/kill -HUP $MAINPID
PrivateDevices=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWriteDirectories=/usr/local/etc/
ProtectHome=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
LockPersonality=yes
NoNewPrivileges=yes
LimitRTPRIO=1
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,38 @@
Role Name
=========
A brief description of the role goes here.
Requirements
------------
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
Role Variables
--------------
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
Dependencies
------------
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
License
-------
BSD
Author Information
------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed).

View File

@ -0,0 +1,5 @@
---
# defaults file for roles/nginx
nginx_user: www-data
nginx_htpasswd_user: admin
nginx_template_file: nginx.conf.j2

View File

@ -0,0 +1,28 @@
- name: Create .htpasswd file
htpasswd:
path: "/etc/nginx/{{ location.basic_auth.file }}"
name: "{{ location.basic_auth.user | default('admin') }}"
password: "{{ location.basic_auth.password }}"
state: present
create: yes
owner: "{{ nginx_user }}"
group: "{{ nginx_user }}"
mode: 0600
loop: "{{ nginx_server.locations }}"
loop_control:
loop_var: location
label: "{{ location.path }}"
when: "location.basic_auth is defined"
notify:
- reload nginx
- name: Copy Nginx config
template:
src: "{{ nginx_template_file }}"
dest: "{{ nginx_config_dir }}/{{ nginx_config_file }}"
owner: "{{ nginx_user }}"
group: "{{ nginx_user }}"
mode: 0644
notify:
- reload nginx

View File

@ -0,0 +1,4 @@
---
- name: Configure Nginx
import_tasks: configure.yml
tags: nginx

View File

@ -0,0 +1,38 @@
{{ ansible_managed | comment }}
{% if nginx_server.name is defined %}
server {
listen {{ nginx_server.port }};
server_name {{ nginx_server.name }};
{% endif %}
{% for location in nginx_server.locations | default([]) %}
location {{ location.path }} {
{% if location.proxy_pass is defined %}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
{% endif %}
{% if location.basic_auth.file is defined %}
auth_basic "Authentication required";
auth_basic_user_file /etc/nginx/{{ location.basic_auth.file }};
{% endif %}
{% if location.proxy_pass is defined %}
proxy_pass http://localhost:{{ location.proxy_pass.port | default('80') }}{{ location.proxy_pass.path }};
{% endif %}
{% if location.return is defined %}
return {{ location.return.code }} {{ location.return.url }};
{% endif %}
}
{% endfor %}
{% if nginx_server.name is defined %}
{% for include_path in nginx_server.includes | default([]) %}
include {{ include_path }};
{% endfor %}
}
{% endif %}

View File

@ -0,0 +1,2 @@
---
# vars file for roles/nginx