Compare commits

..

9 commits

47 changed files with 732 additions and 446 deletions

21
env.yml
View file

@ -1,4 +1,5 @@
domain: viyurz.fr domain: viyurz.fr
ldap_base_dn: dc=viyurz,dc=fr
timezone: "Europe/Paris" timezone: "Europe/Paris"
host_uid: 1000 host_uid: 1000
project_dir: "{{ ansible_env['HOME'] }}/docker-projects/{{ role_name }}" project_dir: "{{ ansible_env['HOME'] }}/docker-projects/{{ role_name }}"
@ -38,14 +39,16 @@ cifs_mounts:
projects: projects:
- authelia
- coturn - coturn
- element - element
- etebase - etebase
- hedgedoc - hedgedoc
- homepage - homepage
- lldap
- mailserver - mailserver
- nginx
- postgres - postgres
- reverse-proxy
- searxng - searxng
- synapse - synapse
- syncthing - syncthing
@ -54,8 +57,10 @@ projects:
projects_to_backup: projects_to_backup:
- authelia
- etebase - etebase
- hedgedoc - hedgedoc
- lldap
- mailserver - mailserver
- postgres - postgres
- synapse - synapse
@ -76,6 +81,7 @@ borg_prune_options: |
# Ports exposed to host # Ports exposed to host
ports: ports:
authelia: 9091
coturn_listening: 3478 coturn_listening: 3478
coturn_tls_listening: 5349 coturn_tls_listening: 5349
coturn_relay_min: 49152 coturn_relay_min: 49152
@ -84,6 +90,7 @@ ports:
etebase: 3735 etebase: 3735
hedgedoc: 8086 hedgedoc: 8086
homepage: 8082 homepage: 8082
lldap: 17170
mailserver_smtp: 1025 mailserver_smtp: 1025
mailserver_smtps: 1465 mailserver_smtps: 1465
mailserver_imaps: 1993 mailserver_imaps: 1993
@ -103,17 +110,18 @@ ports:
# UID in containers # UID in containers
users: users:
authelia: 1008
coturn: 666 coturn: 666
etebase: 373 etebase: 373
hedgedoc: 1004 hedgedoc: 1004
hedgedoc_mysql: 1005 hedgedoc_mysql: 1005
homepage: 8686 homepage: 8686
lldap: 1007
mailserver: 8 mailserver: 8
postgres: 70 postgres: 70
searxng: 977 searxng: 977
searxng_redis: 999 searxng_redis: 999
synapse: 991 synapse: 991
synapse_postgres: 70
syncthing: 1001 syncthing: 1001
syncthing_discosrv: 1002 syncthing_discosrv: 1002
syncthing_relaysrv: 1003 syncthing_relaysrv: 1003
@ -127,24 +135,17 @@ volumes:
etebase_datadir: /mnt/etebasedata etebase_datadir: /mnt/etebasedata
hedgedoc_mysql_datadir: /mnt/hedgedoc/mysql-data hedgedoc_mysql_datadir: /mnt/hedgedoc/mysql-data
hedgedoc_configdir: /mnt/hedgedoc/config hedgedoc_configdir: /mnt/hedgedoc/config
lldap_datadir: /mnt/lldapdata
mailserver_datadir: /mnt/mailserverdata mailserver_datadir: /mnt/mailserverdata
mailserver_tls_certificate_file: "/etc/letsencrypt/live/mail.{{ domain }}/fullchain.pem" mailserver_tls_certificate_file: "/etc/letsencrypt/live/mail.{{ domain }}/fullchain.pem"
mailserver_tls_certificate_key_file: "/etc/letsencrypt/live/mail.{{ domain }}/privkey.pem" mailserver_tls_certificate_key_file: "/etc/letsencrypt/live/mail.{{ domain }}/privkey.pem"
postgres_datadir: /mnt/postgresdata postgres_datadir: /mnt/postgresdata
synapse_datadir: /mnt/synapsedata synapse_datadir: /mnt/synapsedata
synapse_postgres_datadir: /mnt/synapsepgdata
syncthing_datadir: "{{ cifs_mounts['syncthing']['path'] }}" syncthing_datadir: "{{ cifs_mounts['syncthing']['path'] }}"
uptime_kuma_datadir: /mnt/uptimekumadata uptime_kuma_datadir: /mnt/uptimekumadata
vaultwarden_datadir: /mnt/vwdata vaultwarden_datadir: /mnt/vwdata
# Service-specific variables # Service-specific variables
reverse_proxy:
ssl_certificate_file: "/etc/letsencrypt/live/{{ domain }}/fullchain.pem"
ssl_certificate_key_file: "/etc/letsencrypt/live/{{ domain }}/privkey.pem"
ssl_trusted_certificate_file: "/etc/letsencrypt/live/{{ domain }}/chain.pem"
resolver: "185.12.64.12 [a01:4ff:ff00::add:2] [2a01:4ff:ff00::add:1]"
synapse: synapse:
max_upload_size: 50M max_upload_size: 50M

View file

@ -7,6 +7,7 @@
hosts: localhost hosts: localhost
vars: vars:
run_backup: false run_backup: false
run_setup: true
run_update: true run_update: true
vars_prompt: vars_prompt:
- name: selected_projects - name: selected_projects

24
psql-create-db-user.sh Executable file
View file

@ -0,0 +1,24 @@
#!/bin/bash
read -rp "Database: " database
read -rp "User: " user
read -srp "Password: " password
echo ""
read -rp "Create database $database & user $user? [Y/n]: " answer
if ! [[ $answer =~ ^(Y|y)?$ ]]; then
exit 1
fi
stmts=(
"CREATE USER $user PASSWORD '$password'"
"CREATE DATABASE $database WITH OWNER = $user"
"GRANT ALL ON DATABASE $database TO $user"
)
for stmt in "${stmts[@]}"; do
docker exec postgres psql -c "$stmt"
done

View file

@ -0,0 +1,24 @@
- name: "Backup PostgreSQL authelia database"
shell: >
docker exec postgres
pg_dump -c {{ role_name }} |
borg create
--compression lzma
"{{ borg_repodir }}::{{ role_name }}-{now:%Y-%m-%d_%H-%M-%S}"
-
--stdin-name dump_{{ role_name }}.sql
environment:
DOCKER_HOST: "{{ docker_host }}"
BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}"
become: true
- name: Prune borg repository
command:
cmd: |
borg prune
--glob-archives='{{ role_name }}-*'
{{ borg_prune_options }}
{{ borg_repodir }}
environment:
BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}"
become: true

View file

@ -0,0 +1,14 @@
- name: Include backup tasks
include_tasks:
file: backup.yml
when: run_backup | default(false) | bool
- name: Include setup tasks
include_tasks:
file: setup.yml
when: run_setup | default(false) | bool
- name: Include update tasks
include_tasks:
file: update.yml
when: run_update | default(false) | bool

View file

@ -0,0 +1,23 @@
- name: "Create {{ project_dir }} project directory"
file:
path: "{{ project_dir }}"
state: directory
- name: Template docker-compose.yaml & configuration.yml to project directory
template:
src: "{{ item }}"
dest: "{{ project_dir }}/{{ item }}"
owner: "{{ host_uid }}"
group: "{{ host_uid }}"
mode: '640'
loop:
- docker-compose.yaml
- configuration.yml
register: authelia_template_configuration_result
# Separate task because template module cannot chown/chgrp to a non-existing user/group
- name: "Change group of homeserver.yaml to Authelia GID ({{ users['authelia'] + uid_shift }})"
file:
path: "{{ project_dir }}/configuration.yml"
group: "{{ users['authelia'] + uid_shift }}"
become: true

View file

@ -0,0 +1,24 @@
- name: Pull project services
community.docker.docker_compose:
project_src: "{{ project_dir }}"
recreate: never
pull: true
debug: true
when: docker_pull_images | bool
register: authelia_docker_compose_pull_result
- name: Display pulled image(s) name
set_fact:
authelia_pulled_images: "{{ authelia_pulled_images | default([]) + [item.pulled_image.name] }}"
loop: "{{ authelia_docker_compose_pull_result['actions'] | default([]) | selectattr('pulled_image', 'defined') }}"
- name: Include backup tasks
include_tasks:
file: backup.yml
# Make a backup if we didn't already make one and we pulled a new image
when: not run_backup and authelia_pulled_images is defined
- name: Create/Restart project services
community.docker.docker_compose:
project_src: "{{ project_dir }}"
restarted: "{{ authelia_template_configuration_result['changed'] | default(false) | bool }}"

View file

@ -0,0 +1,72 @@
theme: 'auto'
totp:
issuer: '{{ domain }}'
identity_validation:
reset_password:
jwt_secret: '{{ authelia_secrets["jwt_secret"] }}'
authentication_backend:
refresh_interval: '1m'
ldap:
implementation: 'custom'
address: 'ldap://lldap:3890'
base_dn: '{{ ldap_base_dn }}'
users_filter: '(&({username_attribute}={input})(objectClass=person))'
groups_filter: '(member={dn})'
user: '{{ authelia_secrets["ldap_user"] }}'
password: '{{ authelia_secrets["ldap_password"] }}'
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
password_policy:
standard:
enabled: true
min_length: 12
max_length: 128
require_uppercase: true
require_lowercase: true
require_number: true
require_special: true
access_control:
default_policy: 'deny'
rules:
- domain: 'auth.{{ domain }}'
policy: 'bypass'
- domain: 'ldap.{{ domain }}'
policy: 'two_factor'
subject: 'group:lldap_admin'
- domain: 'syncthing.{{ domain }}'
policy: 'two_factor'
subject: 'user:viyurz'
session:
cookies:
- name: 'authelia_session'
domain: '{{ domain }}'
authelia_url: 'https://auth.{{ domain }}'
storage:
encryption_key: '{{ authelia_secrets["encryption_key"] }}'
postgres:
address: postgres.{{ domain }}
database: authelia
username: '{{ authelia_secrets["postgres_user"] }}'
password: '{{ authelia_secrets["postgres_password"] }}'
notifier:
smtp:
address: 'submissions://mail.{{ domain }}:{{ ports["mailserver_smtps"] }}'
username: '{{ authelia_secrets["smtp_user"] }}'
password: '{{ authelia_secrets["smtp_password"] }}'
sender: 'Authelia <authelia@{{ domain }}>'
# identity_providers:

View file

@ -0,0 +1,16 @@
services:
authelia:
container_name: authelia
image: docker.io/authelia/authelia:4
restart: always
user: {{ users['authelia'] }}:{{ users['authelia'] }}
networks:
- authelia
ports:
- 127.0.0.1:{{ ports['authelia'] }}:9091
volumes:
- ./configuration.yml:/config/configuration.yml
networks:
authelia:
name: authelia

View file

@ -0,0 +1,25 @@
- name: "Backup PostgreSQL lldap database & {{ volumes['lldap_datadir'] }} directory"
shell: >
docker exec postgres
pg_dump -c {{ role_name }} |
borg create
--compression lzma
"{{ borg_repodir }}::{{ role_name }}-{now:%Y-%m-%d_%H-%M-%S}"
"{{ volumes['lldap_datadir'] }}"
-
--stdin-name dump_{{ role_name }}.sql
environment:
DOCKER_HOST: "{{ docker_host }}"
BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}"
become: true
- name: Prune borg repository
command:
cmd: |
borg prune
--glob-archives='{{ role_name }}-*'
{{ borg_prune_options }}
{{ borg_repodir }}
environment:
BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}"
become: true

View file

@ -0,0 +1,14 @@
- name: Include backup tasks
include_tasks:
file: backup.yml
when: run_backup | default(false) | bool
- name: Include setup tasks
include_tasks:
file: setup.yml
when: run_setup | default(false) | bool
- name: Include update tasks
include_tasks:
file: update.yml
when: run_update | default(false) | bool

View file

@ -0,0 +1,24 @@
- name: "Create {{ project_dir }} project directory"
file:
path: "{{ project_dir }}"
state: directory
- name: Template docker-compose.yaml & .env to project directory
template:
src: "{{ item }}"
dest: "{{ project_dir }}/{{ item }}"
owner: "{{ host_uid }}"
group: "{{ host_uid }}"
mode: '600'
loop:
- docker-compose.yaml
- .env
- name: "Create (if not exists) directory {{ volumes['lldap_datadir'] }} & set permissions"
file:
path: "{{ volumes['lldap_datadir'] }}"
state: directory
owner: "{{ users['lldap'] + uid_shift }}"
group: "{{ users['lldap'] + uid_shift }}"
mode: '700'
become: true

View file

@ -0,0 +1,23 @@
- name: Pull project services
community.docker.docker_compose:
project_src: "{{ project_dir }}"
recreate: never
pull: true
debug: true
when: docker_pull_images | bool
register: lldap_docker_compose_pull_result
- name: Display pulled image(s) name
set_fact:
lldap_pulled_images: "{{ lldap_pulled_images | default([]) + [item.pulled_image.name] }}"
loop: "{{ lldap_docker_compose_pull_result['actions'] | default([]) | selectattr('pulled_image', 'defined') }}"
- name: Include backup tasks
include_tasks:
file: backup.yml
# Make a backup if we didn't already make one and we pulled a new image
when: not run_backup and lldap_pulled_images is defined
- name: Create/Restart project services
community.docker.docker_compose:
project_src: "{{ project_dir }}"

View file

@ -0,0 +1,7 @@
UID={{ users['lldap'] }}
GID={{ users['lldap'] }}
TZ={{ timezone }}
LLDAP_LDAP_BASE_DN={{ ldap_base_dn }}
LLDAP_JWT_SECRET='{{ lldap_secrets["jwt_secret"] }}'
LLDAP_KEY_SEED='{{ lldap_secrets["key_seed"] }}'
LLDAP_DATABASE_URL='postgres://{{ lldap_secrets["postgres_user"] }}:{{ lldap_secrets["postgres_password"] }}@postgres.{{ domain }}/lldap'

View file

@ -0,0 +1,20 @@
services:
lldap:
container_name: lldap
image: docker.io/lldap/lldap:latest-alpine-rootless
restart: always
user: {{ users['lldap'] }}:{{ users['lldap'] }}
env_file: .env
networks:
- authelia
- mailserver
ports:
- {{ ports['lldap'] }}:17170
volumes:
- {{ volumes['lldap_datadir'] }}:/data
networks:
authelia:
name: authelia
mailserver:
name: mailserver

View file

@ -1,6 +1,6 @@
services: services:
mailserver: mailserver:
image: docker.io/stalwartlabs/mail-server:latest image: docker.io/stalwartlabs/mail-server:v0.6.0
container_name: mailserver container_name: mailserver
restart: always restart: always
ports: ports:

View file

@ -0,0 +1,60 @@
- name:
become: true
block:
- name: Install package nginx
apt:
name: nginx
- name: Delete directories in /etc/nginx/
file:
path: "/etc/nginx/{{ item }}"
state: absent
loop:
- sites-enabled
- snippets
- name: Create directories in /etc/nginx/
file:
path: "/etc/nginx/{{ item }}"
state: directory
loop:
- sites-enabled
- snippets
- name: Template configuration files to /etc/nginx/
template:
src: "{{ item.src }}"
dest: "/etc/nginx/{{ item.path }}"
owner: root
group: root
mode: '644'
with_filetree: ../templates/
when: item.state == 'file'
- name: Get state of file /etc/nginx/dhparam.txt
stat:
path: /etc/nginx/dhparam.txt
register: nginx_stat_dhparam_result
- name: Download dhparam file from Mozilla
get_url:
url: https://ssl-config.mozilla.org/ffdhe2048.txt
dest: /etc/nginx/dhparam.txt
when: not nginx_stat_dhparam_result.stat.exists
- name: Set correct permissions on certificate directories
file:
path: "/etc/letsencrypt/{{ item }}/{{ domain }}"
state: directory
owner: root
group: root
mode: '750'
loop:
- live
- archive
- name: Start/Reload NGINX service
service:
name: nginx
state: reloaded
enabled: yes

View file

@ -0,0 +1,38 @@
user www-data;
worker_processes auto;
worker_rlimit_nofile 1024;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 512;
multi_accept off;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
gzip off;
server_tokens off;
keepalive_timeout 30;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
include /etc/nginx/mime.types;
# Needed to support websocket connections
map $http_upgrade $connection_upgrade {
default upgrade;
'' "";
}
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/snippets/proxy.conf;
include /etc/nginx/snippets/ssl.conf;
include /etc/nginx/snippets/ssl-headers.conf;
include /etc/nginx/sites-enabled/*;
}

View file

@ -0,0 +1,10 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name auth.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['authelia'] }};
}
}

View file

@ -0,0 +1,15 @@
# Redirect HTTP to HTTPS
server {
listen 80 default_server;
listen [::]:80 default_server;
return 308 https://$host$request_uri;
}
# Default HTTPS server
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
return 404;
}

View file

@ -0,0 +1,9 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dl.{{ domain }};
root /var/www/html;
autoindex on;
}

View file

@ -0,0 +1,16 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name element.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['element'] }};
include /etc/nginx/snippets/ssl-headers.conf;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "frame-ancestors 'none'";
}
}

View file

@ -0,0 +1,10 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name etebase.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['etebase'] }};
}
}

View file

@ -0,0 +1,16 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name hedgedoc.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['hedgedoc'] }};
}
location /socket.io/ {
proxy_pass http://127.0.0.1:{{ ports['hedgedoc'] }};
include /etc/nginx/snippets/websocket.conf;
}
}

View file

@ -0,0 +1,25 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name {{ domain }};
location = /.well-known/matrix/server {
default_type application/json;
return 200 '{ "m.server": "matrix.{{ domain }}:443" }';
}
location = /.well-known/matrix/client {
default_type application/json;
include /etc/nginx/snippets/ssl-headers.conf;
add_header Access-Control-Allow-Origin '*';
return 200 '{ "m.homeserver": { "base_url": "https://matrix.{{ domain }}" } }';
}
location / {
proxy_pass http://127.0.0.1:{{ ports['homepage'] }};
}
}

View file

@ -0,0 +1,12 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name jmap.{{ domain }};
location / {
proxy_pass https://127.0.0.1:{{ ports['mailserver_jmap'] }};
include /etc/nginx/snippets/websocket.conf;
}
}

View file

@ -0,0 +1,14 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name ldap.{{ domain }};
include /etc/nginx/snippets/authelia-location.conf;
location / {
proxy_pass http://127.0.0.1:{{ ports['lldap'] }};
include /etc/nginx/snippets/authelia-authrequest.conf;
}
}

View file

@ -0,0 +1,13 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name searx.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['searxng'] }};
include /etc/nginx/snippets/ssl-headers.conf;
add_header Content-Security-Policy "upgrade-insecure-requests; default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'self' https://github.com/searxng/searxng/issues/new; font-src 'self'; frame-ancestors 'self'; base-uri 'self'; connect-src 'self' https://overpass-api.de; img-src 'self' data: https://*.tile.openstreetmap.org; frame-src https://www.youtube-nocookie.com https://player.vimeo.com https://www.dailymotion.com https://www.deezer.com https://www.mixcloud.com https://w.soundcloud.com https://embed.spotify.com";
}
}

View file

@ -0,0 +1,12 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name matrix.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['synapse'] }};
client_max_body_size {{ synapse['max_upload_size'] }};
}
}

View file

@ -0,0 +1,16 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name stdisco.{{ domain }};
ssl_verify_client optional_no_ca;
location / {
proxy_pass http://127.0.0.1:{{ ports['syncthing_discosrv'] }};
proxy_set_header X-Client-Port $remote_port;
proxy_set_header X-SSL-Cert $ssl_client_cert;
include /etc/nginx/snippets/websocket.conf;
}
}

View file

@ -0,0 +1,14 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name syncthing.{{ domain }};
include /etc/nginx/snippets/authelia-location.conf;
location / {
proxy_pass http://127.0.0.1:{{ ports['syncthing_webui'] }};
include /etc/nginx/snippets/authelia-authrequest.conf;
}
}

View file

@ -0,0 +1,12 @@
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name status.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['uptime_kuma'] }};
include /etc/nginx/snippets/websocket.conf;
}
}

View file

@ -0,0 +1,18 @@
upstream vaultwarden {
zone vaultwarden 64k;
server 127.0.0.1:{{ ports['vaultwarden'] }};
keepalive 2;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name vw.{{ domain }};
location / {
proxy_pass http://vaultwarden;
include /etc/nginx/snippets/websocket.conf;
}
}

View file

@ -0,0 +1,15 @@
auth_request /internal/authelia/authz;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
auth_request_set $name $upstream_http_remote_name;
auth_request_set $email $upstream_http_remote_email;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Email $email;
proxy_set_header Remote-Name $name;
auth_request_set $redirection_url $upstream_http_location;
error_page 401 =302 $redirection_url;

View file

@ -0,0 +1,18 @@
location /internal/authelia/authz {
internal;
proxy_pass http://127.0.0.1:{{ ports['authelia'] }}/api/authz/auth-request;
proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Content-Length "";
proxy_set_header Connection "";
proxy_pass_request_body off;
proxy_http_version 1.1;
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
proxy_buffers 4 32k;
client_body_buffer_size 128k;
}

View file

@ -0,0 +1,10 @@
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-URI $request_uri;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Real-IP $remote_addr;

View file

@ -0,0 +1,18 @@
ssl_certificate /etc/letsencrypt/live/{{ domain }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ domain }}/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/{{ domain }}/chain.pem;
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:DHE-RSA-CHACHA20-POLY1305;
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam /etc/nginx/dhparam.txt;
ssl_prefer_server_ciphers on;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;

View file

@ -0,0 +1,2 @@
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

View file

@ -1,79 +0,0 @@
- name:
become: true
block:
- name: Install package nginx
apt:
name: nginx
- name: Template nginx.conf to /etc/nginx/nginx.conf
template:
src: nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '644'
register: nginx_template_nginx_conf_result
- name: Template reverse-proxy.conf to /etc/nginx/sites-available/reverse-proxy.conf
template:
src: reverse-proxy.conf
dest: /etc/nginx/sites-available/reverse-proxy.conf
owner: root
group: root
mode: '644'
register: nginx_template_reverse_proxy_conf_result
- name: Copy ssl-headers.conf to /etc/nginx/conf.d/ssl-headers.conf
copy:
src: files/ssl-headers.conf
dest: /etc/nginx/conf.d/ssl-headers.conf
owner: root
group: root
mode: '644'
register: nginx_copy_ssl_headers_conf_result
- name: Remove all enabled NGINX sites
file:
state: "{{ item }}"
path: "/etc/nginx/sites-enabled"
owner: root
group: root
mode: '755'
loop:
- absent
- directory
- name: Enable reverse-proxy.conf site
file:
state: link
src: /etc/nginx/sites-available/reverse-proxy.conf
dest: /etc/nginx/sites-enabled/reverse-proxy.conf
- name: Get state of file /etc/nginx/dhparam.txt
stat:
path: /etc/nginx/dhparam.txt
register: nginx_stat_dhparam_result
- name: Download dhparam file from Mozilla
get_url:
url: https://ssl-config.mozilla.org/ffdhe2048.txt
dest: /etc/nginx/dhparam.txt
when: not nginx_stat_dhparam_result.stat.exists
- name: Set correct permissions on certificate directories
file:
path: "/etc/letsencrypt/{{ item }}/{{ domain }}"
state: directory
owner: root
group: root
mode: '750'
loop:
- live
- archive
- name: Start/Reload NGINX service
service:
name: nginx
# Reload if conf changed, if not make sure it is started
state: "{{ (nginx_template_nginx_conf_result['changed'] or nginx_template_reverse_proxy_conf_result['changed'] or nginx_copy_ssl_headers_conf_result['changed']) | ternary('reloaded', 'started') }}"
enabled: yes

View file

@ -1,84 +0,0 @@
user www-data;
worker_processes auto;
worker_rlimit_nofile 1024;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 512;
multi_accept off;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
gzip off;
server_tokens off;
keepalive_timeout 30;
include /etc/nginx/mime.types;
##
# SSL Settings
##
ssl_certificate {{ reverse_proxy['ssl_certificate_file'] }};
ssl_certificate_key {{ reverse_proxy['ssl_certificate_key_file'] }};
ssl_trusted_certificate {{ reverse_proxy['ssl_trusted_certificate_file'] }};
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:DHE-RSA-CHACHA20-POLY1305;
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam /etc/nginx/dhparam.txt;
ssl_prefer_server_ciphers on;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Headers
##
resolver {{ reverse_proxy['resolver'] }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Scheme $scheme;
# Needed to support websocket connections
# See: https://nginx.org/en/docs/http/websocket.html
# Instead of "close" as stated in the above link we send an empty value.
# Else all keepalive connections will not work.
map $http_upgrade $connection_upgrade {
default upgrade;
'' "";
}
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

View file

@ -1,228 +0,0 @@
# Redirect HTTP to HTTPS
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 308 https://$host$request_uri;
}
# Default HTTPS server
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name _;
server_name_in_redirect off;
return 404;
}
# Homepage
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name {{ domain }};
location = /.well-known/matrix/server {
default_type application/json;
return 200 '{ "m.server": "matrix.{{ domain }}:443" }';
}
location = /.well-known/matrix/client {
default_type application/json;
include /etc/nginx/conf.d/ssl-headers.conf;
add_header Access-Control-Allow-Origin '*';
return 200 '{ "m.homeserver": { "base_url": "https://matrix.{{ domain }}" } }';
}
location / {
proxy_pass http://127.0.0.1:{{ ports['homepage'] }};
}
}
# Downloads
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name dl.{{ domain }};
root /var/www/html;
autoindex on;
}
# Element
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name element.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['element'] }};
include /etc/nginx/conf.d/ssl-headers.conf;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "frame-ancestors 'none'";
}
}
# Etebase
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name etebase.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['etebase'] }};
}
}
# Hedgedoc
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name hedgedoc.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['hedgedoc'] }};
}
location /socket.io/ {
proxy_pass http://127.0.0.1:{{ ports['hedgedoc'] }};
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
}
# JMAP
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mail.{{ domain }};
location / {
proxy_pass https://127.0.0.1:{{ ports['mailserver_jmap'] }};
# Websocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
# SearXNG
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name searx.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['searxng'] }};
include /etc/nginx/conf.d/ssl-headers.conf;
add_header Content-Security-Policy "upgrade-insecure-requests; default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'self' https://github.com/searxng/searxng/issues/new; font-src 'self'; frame-ancestors 'self'; base-uri 'self'; connect-src 'self' https://overpass-api.de; img-src 'self' data: https://*.tile.openstreetmap.org; frame-src https://www.youtube-nocookie.com https://player.vimeo.com https://www.dailymotion.com https://www.deezer.com https://www.mixcloud.com https://w.soundcloud.com https://embed.spotify.com";
}
}
# Synapse
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name matrix.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['synapse'] }};
# Nginx by default only allows file uploads up to 1M in size
# Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
client_max_body_size {{ synapse['max_upload_size'] }};
}
}
# Syncthing Discovery
upstream stdisco.{{ domain }} {
# Local IP address:port for discovery server
server 127.0.0.1:{{ ports['syncthing_discosrv'] }};
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name stdisco.{{ domain }};
ssl_verify_client optional_no_ca;
location / {
proxy_pass http://stdisco.{{ domain }};
proxy_set_header X-Client-Port $remote_port;
proxy_set_header X-SSL-Cert $ssl_client_cert;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
}
# Uptime Kuma
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name status.{{ domain }};
location / {
proxy_pass http://127.0.0.1:{{ ports['uptime_kuma'] }};
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
# Vaultwarden
upstream vaultwarden-default {
zone vaultwarden-default 64k;
server 127.0.0.1:{{ ports['vaultwarden'] }};
keepalive 2;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name vw.{{ domain }};
location / {
proxy_pass http://vaultwarden-default;
# Websocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}

View file

@ -1,22 +1,15 @@
- name: Backup PostgreSQL database - name: "Backup PostgreSQL synapse database & {{ volumes['synapse_datadir'] }} directory"
community.docker.docker_container_exec: shell: >
container: synapse-postgres docker exec postgres
docker_host: "{{ docker_host }}" pg_dump -c {{ role_name }} |
command: | borg create
pg_dump -c --compression lzma
-d synapse "{{ borg_repodir }}::{{ role_name }}-{now:%Y-%m-%d_%H-%M-%S}"
-U synapse "{{ volumes['synapse_datadir'] }}"
-f /var/lib/postgresql/data/synapse-dump.sql -
--stdin-name dump_{{ role_name }}.sql
- name: Create borg backup
command:
cmd: |
borg create
--compression=lzma
"{{ borg_repodir }}::{{ role_name }}-{now:%Y-%m-%d_%H-%M-%S}"
{{ volumes['synapse_datadir'] }}/media_store
{{ volumes['synapse_postgres_datadir'] }}/synapse-dump.sql
environment: environment:
DOCKER_HOST: "{{ docker_host }}"
BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}" BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}"
become: true become: true

View file

@ -36,15 +36,6 @@
mode: '770' mode: '770'
become: true become: true
- name: "Create directory {{ volumes['synapse_postgres_datadir'] }} with correct permissions"
file:
path: "{{ volumes['synapse_postgres_datadir'] }}"
state: directory
owner: "{{ users['synapse_postgres'] + uid_shift }}"
group: "{{ users['synapse_postgres'] + uid_shift }}"
mode: '700'
become: true
- name: Pull project services - name: Pull project services
community.docker.docker_compose: community.docker.docker_compose:
project_src: "{{ project_dir }}" project_src: "{{ project_dir }}"

View file

@ -1,17 +1,4 @@
services: services:
postgres:
container_name: synapse-postgres
image: docker.io/library/postgres:alpine
restart: always
user: {{ users['synapse_postgres'] }}:{{ users['synapse_postgres'] }}
environment:
LANG: C
POSTGRES_INITDB_ARGS: "--locale=C --encoding=UTF8"
POSTGRES_USER: synapse
POSTGRES_PASSWORD: {{ synapse_secrets['postgres_password'] }}
volumes:
- {{ volumes['synapse_postgres_datadir'] }}:/var/lib/postgresql/data
synapse: synapse:
container_name: synapse container_name: synapse
image: docker.io/matrixdotorg/synapse:latest image: docker.io/matrixdotorg/synapse:latest

View file

@ -33,10 +33,10 @@ max_avatar_size: 2M
database: database:
name: psycopg2 name: psycopg2
args: args:
user: synapse user: '{{ synapse_secrets["postgres_user"] }}'
password: {{ synapse_secrets['postgres_password'] }} password: '{{ synapse_secrets["postgres_password"] }}'
dbname: synapse dbname: synapse
host: synapse-postgres host: 'postgres.{{ domain }}'
cp_min: 5 cp_min: 5
cp_max: 10 cp_max: 10

View file

@ -1,3 +1,5 @@
# To generate random secret: openssl rand -base64 <length>
ansible_become_password: ansible_become_password:
borg_passphrase: borg_passphrase:
@ -6,19 +8,38 @@ cifs_credentials:
username: username:
password: password:
# To generate random secret: openssl rand -base64 50
authelia_secrets:
# Encryption key for the database, must be saved
encryption_key:
jwt_secret:
# LDAP bind dn
ldap_user:
ldap_password:
postgres_user:
postgres_password:
smtp_user:
smtp_password:
coturn_secrets: coturn_secrets:
static_auth_secret: static_auth_secret:
hedgedoc_secrets: hedgedoc_secrets:
mysql_root_password: mysql_root_password:
lldap_secrets:
jwt_secret:
key_seed:
postgres_user:
postgres_password:
searxng_secrets: searxng_secrets:
searxng_secret: searxng_secret:
synapse_secrets: synapse_secrets:
smtp_user: smtp_user:
smtp_pass: smtp_pass:
postgres_user:
postgres_password: postgres_password:
turn_shared_secret: "{{ coturn_secrets['static_auth_secret'] }}" turn_shared_secret: "{{ coturn_secrets['static_auth_secret'] }}"
macaroon_secret_key: macaroon_secret_key: