diff --git a/ansible/roles/deploy_container_healthchecks/README.md b/ansible/roles/deploy_container_healthchecks/README.md
new file mode 100644
index 0000000..07a7131
--- /dev/null
+++ b/ansible/roles/deploy_container_healthchecks/README.md
@@ -0,0 +1,72 @@
+# Role: deploy_container_traefik
+
+## Purpose
+This role deploys and configures a Healthchecks.io Container with Docker Compose
+
+## Variables
+
+### Default Variables (`defaults/main.yml`)
+```yaml
+container_healthchecks_domain: health.example.com
+container_healthchecks_version: v3.10
+container_healthchecks_email_from: "healthchecks@your.domain.com"
+container_healthchecks_email_host: smtp.your.domain.com
+container_healthchecks_email_password: your_email_password
+container_healthchecks_email_user: healthchecks@your.domain.com
+container_healthchecks_email_port: 587
+container_healthchecks_email_tls: true
+container_healthchecks_email_user_verification: true
+container_healthchecks_secret_key: your_secret_key
+container_healthchecks_site_name: "Healthchecks"
+```
+
+### Secret Key Generation
+Generate a secure secret key for Django
+You can use
+´´´
+python3 -c "import secrets; print(secrets.token_urlsafe(50))"
+´´´
+to generate a new key or use an online Generator like https://djecrety.ir/
+
+### Static Variables (`vars/main.yml`)
+```yaml
+container_base_dir: /opt/docker/healthchecks
+```
+
+### Role Usage
+
+```yaml
+- name: Deploy Healthchecks.io container
+ hosts: docker
+ roles:
+ - role: deploy_container_healthchecks
+```
+
+## Requirements
+
+* Linux system (tested on Debian)
+* Docker Engine
+* Docker Compose v2 plugin (`docker compose` CLI)
+* Ansible 2.11 or higher
+* `community.docker` collection
+
+Install the required collection:
+
+```bash
+ansible-galaxy collection install community.docker
+```
+
+Or via `requirements.yml`:
+
+```yaml
+collections:
+ - name: community.docker
+ version: ">=3.4.0"
+```
+
+## Authors
+
+* Kevin Heyer
+ 📧 [kevin.heyer@wira-gmbh.de](mailto:kevin.heyer@wira-gmbh.de)
+
+```
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_healthchecks/defaults/main.yml b/ansible/roles/deploy_container_healthchecks/defaults/main.yml
new file mode 100644
index 0000000..6d5eff9
--- /dev/null
+++ b/ansible/roles/deploy_container_healthchecks/defaults/main.yml
@@ -0,0 +1,13 @@
+container_healthchecks_domain: health.example.com
+container_healthchecks_version: v3.10
+container_healthchecks_email_from: "healthchecks@your.domain.com"
+container_healthchecks_email_host: smtp.your.domain.com
+container_healthchecks_email_password: your_email_password
+container_healthchecks_email_user: healthchecks@your.domain.com
+container_healthchecks_email_port: 587
+container_healthchecks_email_tls: true
+container_healthchecks_email_user_verification: true
+# Generate a secure secret key for Django
+# You can use `python3 -c "import secrets; print(secrets.token_urlsafe(50))"` to generate a new key
+container_healthchecks_secret_key: your_secret_key
+container_healthchecks_site_name: "Healthchecks"
diff --git a/ansible/roles/deploy_container_healthchecks/files/.gitkeep b/ansible/roles/deploy_container_healthchecks/files/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_healthchecks/handlers/main.yml b/ansible/roles/deploy_container_healthchecks/handlers/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_healthchecks/meta/main.yml b/ansible/roles/deploy_container_healthchecks/meta/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_healthchecks/tasks/main.yml b/ansible/roles/deploy_container_healthchecks/tasks/main.yml
new file mode 100644
index 0000000..bef8104
--- /dev/null
+++ b/ansible/roles/deploy_container_healthchecks/tasks/main.yml
@@ -0,0 +1,28 @@
+---
+- name: Create Container Structure
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}"
+ state: directory
+ mode: '0775'
+ recurse: true
+
+- name: Deploy Docker Compose and .env files
+ ansible.builtin.template:
+ src: '{{ item.src }}'
+ dest: '{{ item.dest }}'
+ mode: '0775'
+ loop:
+ - {src: '.env.j2', dest: '{{ container_base_dir }}/.env'}
+ - {src: 'docker-compose.yml.j2', dest: '{{ container_base_dir }}/docker-compose.yml'}
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
diff --git a/ansible/roles/deploy_container_healthchecks/templates/.env.j2 b/ansible/roles/deploy_container_healthchecks/templates/.env.j2
new file mode 100644
index 0000000..9721a0d
--- /dev/null
+++ b/ansible/roles/deploy_container_healthchecks/templates/.env.j2
@@ -0,0 +1,14 @@
+ALLOWED_HOSTS={{ container_healthchecks_domain }}
+HEALTHCHECKS_DOMAIN={{ container_healthchecks_domain }}
+HEALTHCHECKS_VERSION={{ container_healthchecks_version }}
+SITE_ROOT=https://{{ container_healthchecks_domain }}
+SECRET_KEY={{ container_healthchecks_secret_key }}
+EMAIL_USE_TLS={{ container_healthchecks_email_tls }}
+EMAIL_PORT={{ container_healthchecks_email_port }}
+EMAIL_HOST_USER={{ container_healthchecks_email_user }}
+EMAIL_HOST_PASSWORD={{ container_healthchecks_email_password }}
+EMAIL_HOST={{ container_healthchecks_email_host }}
+DEFAULT_FROM_EMAIL={{ container_healthchecks_email_from }}
+EMAIL_USE_VERIFICATION={{ container_healthchecks_email_user_verification }}
+PING_ENDPOINT=https://{{ container_healthchecks_domain }}/ping/
+SITE_NAME={{ container_healthchecks_site_name }}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_healthchecks/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_healthchecks/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..2882bc9
--- /dev/null
+++ b/ansible/roles/deploy_container_healthchecks/templates/docker-compose.yml.j2
@@ -0,0 +1,49 @@
+services:
+ healthchecks:
+ image: healthchecks/healthchecks:${HEALTHCHECKS_VERSION}
+ container_name: healthchecks
+ restart: always
+ volumes:
+ - db:/data
+ networks:
+ traefik:
+ environment:
+ - DB=sqlite
+ - DB_NAME=/data/hc.sqlite
+ - DEBUG=False
+ - DEFAULT_FROM_EMAIL=${DEFAULT_FROM_EMAIL}
+ - EMAIL_HOST=${EMAIL_HOST}
+ - EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD}
+ - EMAIL_HOST_USER=${EMAIL_HOST_USER}
+ - EMAIL_PORT=${EMAIL_PORT}
+ - EMAIL_USE_TLS=${EMAIL_USE_TLS}
+ - EMAIL_USE_VERIFICATION=${EMAIL_USE_VERIFICATION}
+ - SECRET_KEY=${SECRET_KEY}
+ - SITE_ROOT=${SITE_ROOT}
+ - ALLOWED_HOSTS=${ALLOWED_HOSTS}
+ - PING_ENDPOINT=${PING_ENDPOINT}
+ - SITE_NAME=${SITE_NAME}
+{% if container_traefik_auth == 'sso' %}
+ - REMOTE_USER_HEADER=HTTP_REMOTE_EMAIL
+{% endif %}
+ labels:
+ - "traefik.enable=true"
+ - "traefik.docker.network=traefik"
+ - "traefik.http.routers.healthchecks.entrypoints=http"
+ - "traefik.http.routers.healthchecks.rule=Host(`${HEALTHCHECKS_DOMAIN}`)"
+ - "traefik.http.middlewares.healthchecks-https-redirect.redirectscheme.scheme=https"
+ - "traefik.http.routers.healthchecks.middlewares=traefik-https-redirect"
+ - "traefik.http.routers.healthchecks-secure.entrypoints=https"
+ - "traefik.http.routers.healthchecks-secure.rule=Host(`${HEALTHCHECKS_DOMAIN}`)"
+ - "traefik.http.routers.healthchecks-secure.tls=true"
+ - "traefik.http.services.healthchecks.loadbalancer.server.port=8000"
+{% if container_traefik_auth == 'sso' %}
+ - "traefik.http.routers.healthchecks-secure.middlewares=middlewares-authelia@file"
+{% endif %}
+
+networks:
+ traefik:
+ external: true
+
+volumes:
+ db:
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_healthchecks/vars/main.yml b/ansible/roles/deploy_container_healthchecks/vars/main.yml
new file mode 100644
index 0000000..7ccedd8
--- /dev/null
+++ b/ansible/roles/deploy_container_healthchecks/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir : "/opt/docker/healthchecks"
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_ittools/defaults/main.yml b/ansible/roles/deploy_container_ittools/defaults/main.yml
new file mode 100644
index 0000000..973fdda
--- /dev/null
+++ b/ansible/roles/deploy_container_ittools/defaults/main.yml
@@ -0,0 +1,5 @@
+############
+# IT-Tools #
+############
+container_ittools_version: latest
+container_ittools_domain: ittools.example.com
diff --git a/ansible/roles/deploy_container_ittools/files/.gitkeep b/ansible/roles/deploy_container_ittools/files/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_ittools/handlers/main.yml b/ansible/roles/deploy_container_ittools/handlers/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_ittools/meta/main.yml b/ansible/roles/deploy_container_ittools/meta/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_ittools/tasks/main.yml b/ansible/roles/deploy_container_ittools/tasks/main.yml
new file mode 100644
index 0000000..d4b4c2e
--- /dev/null
+++ b/ansible/roles/deploy_container_ittools/tasks/main.yml
@@ -0,0 +1,27 @@
+---
+- name: Ensure data directories exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/"
+ state: directory
+ mode: '0755'
+
+- name: Deploy Docker Compose and .env files
+ ansible.builtin.template:
+ src: '{{ item.src }}'
+ dest: '{{ item.dest }}'
+ mode: '0775'
+ loop:
+ - {src: '.env.j2', dest: '{{ container_base_dir }}/.env'}
+ - {src: 'docker-compose.yml.j2', dest: '{{ container_base_dir }}/docker-compose.yml'}
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
diff --git a/ansible/roles/deploy_container_ittools/templates/.env.j2 b/ansible/roles/deploy_container_ittools/templates/.env.j2
new file mode 100644
index 0000000..df01641
--- /dev/null
+++ b/ansible/roles/deploy_container_ittools/templates/.env.j2
@@ -0,0 +1,2 @@
+ITTOOLS_VERSION: {{ container_ittools_version }}
+ITTOOLS_DOMAIN: {{ container_ittools_domain }}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_ittools/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_ittools/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..4ef8616
--- /dev/null
+++ b/ansible/roles/deploy_container_ittools/templates/docker-compose.yml.j2
@@ -0,0 +1,29 @@
+---
+services:
+ it-tools:
+ image: corentinth/it-tools:${ITTOOLS_VERSION}
+ container_name: ittools
+ restart: unless-stopped
+ networks:
+ traefik:
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.ittools.entrypoints=http"
+ - "traefik.http.routers.ittools.rule=Host(`${ITTOOLS_DOMAIN:?error}`)"
+ - "traefik.http.middlewares.ittools-https-redirect.redirectscheme.scheme=https"
+ - "traefik.http.routers.ittools.middlewares=traefik-https-redirect"
+ - "traefik.http.routers.ittools-secure.entrypoints=https"
+ - "traefik.http.routers.ittools-secure.rule=Host(`${ITTOOLS_DOMAIN:?error}`)"
+ - "traefik.http.routers.ittools-secure.tls=true"
+ - "traefik.http.services.ittools.loadbalancer.server.port=80"
+ - "traefik.docker.network=traefik"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -fsS -m 5 http://127.0.0.1/ >/dev/null || exit 1"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 30s
+
+networks:
+ traefik:
+ external: true
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_ittools/vars/main.yml b/ansible/roles/deploy_container_ittools/vars/main.yml
new file mode 100644
index 0000000..5dd3918
--- /dev/null
+++ b/ansible/roles/deploy_container_ittools/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir: /opt/docker/ittools
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_limesurvey/defaults/main.yml b/ansible/roles/deploy_container_limesurvey/defaults/main.yml
new file mode 100644
index 0000000..0dd9e4f
--- /dev/null
+++ b/ansible/roles/deploy_container_limesurvey/defaults/main.yml
@@ -0,0 +1,11 @@
+##############
+# Limesurvey #
+##############
+container_limesurvey_version: latest
+container_limesurvey_domain: limesurvey.example.com
+container_limesurvey_user: limesurvey
+container_limesurvey_password: limesurvey_password
+container_limesurvey_name: "LimeSurvey Admin"
+container_limesurvey_email: admin@example.com
+container_mariadb_version: 10.5
+container_mariadb_password: mariadb_password
diff --git a/ansible/roles/deploy_container_limesurvey/files/.gitkeep b/ansible/roles/deploy_container_limesurvey/files/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_limesurvey/handlers/main.yml b/ansible/roles/deploy_container_limesurvey/handlers/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_limesurvey/meta/main.yml b/ansible/roles/deploy_container_limesurvey/meta/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_limesurvey/tasks/main.yml b/ansible/roles/deploy_container_limesurvey/tasks/main.yml
new file mode 100644
index 0000000..314363b
--- /dev/null
+++ b/ansible/roles/deploy_container_limesurvey/tasks/main.yml
@@ -0,0 +1,32 @@
+---
+- name: Ensure data directories exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/data/{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - "plugins"
+ - "upload"
+ - "sessions"
+ - "data/images"
+
+- name: Deploy Docker Compose and .env files
+ ansible.builtin.template:
+ src: "{{ item.src }}"
+ dest: "{{ container_base_dir }}/{{ item.dest }}"
+ mode: '0644'
+ loop:
+ - { src: 'docker-compose.yml.j2', dest: 'docker-compose.yml' }
+ - { src: '.env.j2', dest: '.env' }
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
diff --git a/ansible/roles/deploy_container_limesurvey/templates/.env.j2 b/ansible/roles/deploy_container_limesurvey/templates/.env.j2
new file mode 100644
index 0000000..c93184a
--- /dev/null
+++ b/ansible/roles/deploy_container_limesurvey/templates/.env.j2
@@ -0,0 +1,8 @@
+LIMESURVEY_VERSION={{ container_limesurvey_version }}
+LIMESURVEY_DOMAIN={{ container_limesurvey_domain }}
+LIMESURVEY_USER={{ container_limesurvey_user }}
+LIMESURVEY_PASSWORD={{ container_limesurvey_password }}
+LIMESURVEY_NAME={{ container_limesurvey_name }}
+LIMESURVEY_EMAIL={{ container_limesurvey_email }}
+MARIADB_VERSION={{ container_mariadb_version }}
+MARIADB_PASSWORD={{ container_mariadb_password }}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_limesurvey/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_limesurvey/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..7057041
--- /dev/null
+++ b/ansible/roles/deploy_container_limesurvey/templates/docker-compose.yml.j2
@@ -0,0 +1,64 @@
+---
+services:
+ limesurvey:
+ image: adamzammit/limesurvey:${LIMESURVEY_VERSION}
+ container_name: limesurvey
+ restart: always
+ networks:
+ traefik:
+ limesurvey:
+ volumes:
+ - ./data/plugins:/var/www/html/plugins
+ - ./data/upload:/var/www/html/upload
+ - ./data/config:/var/www/html/application/config
+ - ./data/sessions:/var/lime/sessions
+ - ./data/images/logo.png:/var/www/html/assets/images/logo-icon-white.png
+ - ./data/images/logo.png:/var/www/html/assets/images/Limesurvey_logo-big.png
+ - ./data/images/logo.png:/var/www/html/assets/images/Logo_LimeSurvey.png
+ - ./data/images/logo.png:/var/www/html/assets/images/__Limesurvey_logo.png
+ - ./data/images/logo.png:/var/www/html/assets/images/logo-white.png
+ - ./data/images/logo.png:/var/www/html/tmp/assets/aa2d5407/survey_list_header.png
+ environment:
+ LIMESURVEY_DB_PASSWORD: ${MARIADB_PASSWORD}
+ LIMESURVEY_ADMIN_USER: ${LIMESURVEY_USER}
+ LIMESURVEY_ADMIN_PASSWORD: ${LIMESURVEY_PASSWORD}
+ LIMESURVEY_ADMIN_NAME: ${LIMESURVEY_NAME}
+ LIMESURVEY_ADMIN_EMAIL: ${LIMESURVEY_EMAIL}
+ TZ: Europe/Berlin
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.limesurvey.entrypoints=https"
+ - "traefik.http.routers.limesurvey.rule=Host(`${LIMESURVEY_DOMAIN}`)"
+ - "traefik.http.routers.limesurvey.tls=true"
+ - "traefik.http.routers.limesurvey.tls.certresolver=letsencrypt"
+ - "traefik.http.services.limesurvey.loadbalancer.server.port=80"
+ - "traefik.docker.network=traefik"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -fsS -m 5 http://127.0.0.1/ >/dev/null || exit 1"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 60s
+
+ mysql:
+ image: mariadb:${MARIADB_VERSION}
+ container_name: limesurvey_db
+ restart: always
+ networks:
+ limesurvey:
+ environment:
+ MYSQL_ROOT_PASSWORD: ${MARIADB_PASSWORD}
+ volumes:
+ - ./data/mysql:/var/lib/mysql
+ healthcheck:
+ test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
+ interval: 10s
+ timeout: 5s
+ retries: 10
+ start_period: 60s
+
+networks:
+ traefik:
+ external: true
+ limesurvey:
+ driver: bridge
diff --git a/ansible/roles/deploy_container_limesurvey/vars/main.yml b/ansible/roles/deploy_container_limesurvey/vars/main.yml
new file mode 100644
index 0000000..74f6233
--- /dev/null
+++ b/ansible/roles/deploy_container_limesurvey/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir: /opt/docker/limesurvey
diff --git a/ansible/roles/deploy_container_netalertx/defaults/main.yml b/ansible/roles/deploy_container_netalertx/defaults/main.yml
new file mode 100644
index 0000000..04aced8
--- /dev/null
+++ b/ansible/roles/deploy_container_netalertx/defaults/main.yml
@@ -0,0 +1,5 @@
+#############
+# NetAlertX #
+#############
+container_netalertx_version: latest
+container_netalertx_domain: netalertx.example.com
diff --git a/ansible/roles/deploy_container_netalertx/files/.gitkeep b/ansible/roles/deploy_container_netalertx/files/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_netalertx/handlers/main.yml b/ansible/roles/deploy_container_netalertx/handlers/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_netalertx/meta/main.yml b/ansible/roles/deploy_container_netalertx/meta/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_netalertx/tasks/main.yml b/ansible/roles/deploy_container_netalertx/tasks/main.yml
new file mode 100644
index 0000000..afbe6fc
--- /dev/null
+++ b/ansible/roles/deploy_container_netalertx/tasks/main.yml
@@ -0,0 +1,31 @@
+---
+- name: Ensure data directories exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/data/{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - "config"
+ - "db"
+ - "logs"
+
+- name: Deploy Docker Compose and .env files
+ ansible.builtin.template:
+ src: "{{ item.src }}"
+ dest: "{{ container_base_dir }}/{{ item.dest }}"
+ mode: '0644'
+ loop:
+ - { src: 'docker-compose.yml.j2', dest: 'docker-compose.yml' }
+ - { src: '.env.j2', dest: '.env' }
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
diff --git a/ansible/roles/deploy_container_netalertx/templates/.env.j2 b/ansible/roles/deploy_container_netalertx/templates/.env.j2
new file mode 100644
index 0000000..0dd4206
--- /dev/null
+++ b/ansible/roles/deploy_container_netalertx/templates/.env.j2
@@ -0,0 +1,2 @@
+NETALERTX_VERSION={{ container_netalertx_version }}
+NETALERTX_DOMAIN={{ container_netalertx_domain }}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_netalertx/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_netalertx/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..2705107
--- /dev/null
+++ b/ansible/roles/deploy_container_netalertx/templates/docker-compose.yml.j2
@@ -0,0 +1,37 @@
+services:
+ netalertx:
+ image: jokobsk/netalertx:${NETALERTX_VERSION}
+ container_name: netalertx
+ restart: unless-stopped
+ networks:
+ traefik:
+ volumes:
+ - ./data/config:/app/config
+ - ./data/db:/app/db
+ - ./data/logs:/app/front/log
+ environment:
+ - TZ=Europe/Berlin
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.netalertx.entrypoints=http"
+ - "traefik.http.routers.netalertx.rule=Host(`${NETALERTX_DOMAIN}`)"
+ - "traefik.http.middlewares.netalertx-https-redirect.redirectscheme.scheme=https"
+ - "traefik.http.routers.netalertx.middlewares=traefik-https-redirect"
+ - "traefik.http.routers.netalertx-secure.entrypoints=https"
+ - "traefik.http.routers.netalertx-secure.rule=Host(`${NETALERTX_DOMAIN}`)"
+ - "traefik.http.routers.netalertx-secure.tls=true"
+ - "traefik.http.services.netalertx.loadbalancer.server.port=20211"
+{% if container_traefik_auth == 'sso' %}
+ - "traefik.http.routers.netalertx-secure.middlewares=middlewares-authelia@file"
+{% endif %}
+ - "traefik.docker.network=traefik"
+ healthcheck:
+ test: ["CMD", "curl", "-f", "127.0.0.1:20211/php/server/query_json.php?file=app_state.json"]
+ interval: 30s
+ timeout: 5s
+ start_period: 15s
+ retries: 2
+
+networks:
+ traefik:
+ external: true
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_netalertx/vars/main.yml b/ansible/roles/deploy_container_netalertx/vars/main.yml
new file mode 100644
index 0000000..632933a
--- /dev/null
+++ b/ansible/roles/deploy_container_netalertx/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir: /opt/docker/netalertx
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_netbox/defaults/main.yml b/ansible/roles/deploy_container_netbox/defaults/main.yml
new file mode 100644
index 0000000..eb1a081
--- /dev/null
+++ b/ansible/roles/deploy_container_netbox/defaults/main.yml
@@ -0,0 +1,13 @@
+##########
+# Netbox #
+##########
+container_netbox_postgres_version: 16
+container_netbox_postgres_db: netbox
+container_netbox_postgres_user: netbox
+container_netbox_postgres_password: random_password_here
+
+container_netbox_redis_version: 7
+
+container_netbox_version: latest
+container_netbox_secret_key: "random_secret_key_here"
+container_netbox_domain: netbox.example.com
diff --git a/ansible/roles/deploy_container_netbox/files/Dockerfile-Plugins b/ansible/roles/deploy_container_netbox/files/Dockerfile-Plugins
new file mode 100644
index 0000000..4302f29
--- /dev/null
+++ b/ansible/roles/deploy_container_netbox/files/Dockerfile-Plugins
@@ -0,0 +1,14 @@
+ARG NETBOX_VERSION
+
+FROM netboxcommunity/netbox:$NETBOX_VERSION
+
+COPY ./data/plugin_requirements.txt /opt/netbox/
+RUN /usr/local/bin/uv pip install -r /opt/netbox/plugin_requirements.txt
+
+# For the toplogical_view Plugin
+RUN mkdir -p /opt/netbox/netbox/static/netbox_topology_views/img
+
+# These lines are only required if your plugin has its own static files.
+COPY ./data/configuration/plugins.py /etc/netbox/config/plugins.py
+RUN DEBUG="true" SECRET_KEY="dummydummydummydummydummydummydummydummydummydummy" \
+ /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_netbox/files/plugin_requirements.txt b/ansible/roles/deploy_container_netbox/files/plugin_requirements.txt
new file mode 100644
index 0000000..bc6462e
--- /dev/null
+++ b/ansible/roles/deploy_container_netbox/files/plugin_requirements.txt
@@ -0,0 +1,3 @@
+netbox-plugin-dns
+netbox-topology-views
+netbox-qrcode
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_netbox/files/plugins.py b/ansible/roles/deploy_container_netbox/files/plugins.py
new file mode 100644
index 0000000..500223f
--- /dev/null
+++ b/ansible/roles/deploy_container_netbox/files/plugins.py
@@ -0,0 +1,13 @@
+PLUGINS = [
+ "netbox_dns",
+ "netbox_topology_views",
+ "netbox_qrcode"
+]
+
+PLUGINS_CONFIG = {
+ 'netbox_qrcode': {
+ 'models': {
+ 'dcim.device': {}, # QR-Codes für Geräte aktivieren
+ }
+ }
+}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_netbox/handlers/main.yml b/ansible/roles/deploy_container_netbox/handlers/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_netbox/meta/main.yml b/ansible/roles/deploy_container_netbox/meta/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_netbox/tasks/main.yml b/ansible/roles/deploy_container_netbox/tasks/main.yml
new file mode 100644
index 0000000..383ddb2
--- /dev/null
+++ b/ansible/roles/deploy_container_netbox/tasks/main.yml
@@ -0,0 +1,51 @@
+---
+- name: Ensure data directories exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - "data/backup"
+ - "data/configuration"
+ - "data/netbox"
+ - "data/static/netbox_topology_views/img"
+ - "data/static/netbox_topology_views/js"
+ - "data/static/netbox_topology_views/css"
+
+- name: Ensure Docker BuildX Plugin is installed
+ ansible.builtin.apt:
+ package:
+ - docker-buildx-plugin
+ state: present
+ cache_valid_time: 3600
+
+- name: Copy Files
+ ansible.builtin.copy:
+ src: '{{ item.src }}'
+ dest: '{{ item.dest }}'
+ mode: "0664"
+ loop:
+ - {src: 'Dockerfile-Plugins', dest: '{{ container_base_dir }}/Dockerfile-Plugins'}
+ - {src: 'plugins.py', dest: '{{ container_base_dir }}/data/configuration/plugins.py'}
+ - {src: 'plugin_requirements.txt', dest: '{{ container_base_dir }}/data/plugin_requirements.txt'}
+
+- name: Render Template Files
+ ansible.builtin.template:
+ src: '{{ item.src }}'
+ dest: '{{ item.dest }}'
+ mode: "0664"
+ loop:
+ - {src: .env.j2, dest: '{{ container_base_dir }}/.env'}
+ - {src: 'docker-compose.yml.j2', dest: '{{ container_base_dir }}/docker-compose.yml'}
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_netbox/templates/.env.j2 b/ansible/roles/deploy_container_netbox/templates/.env.j2
new file mode 100644
index 0000000..a0f25d1
--- /dev/null
+++ b/ansible/roles/deploy_container_netbox/templates/.env.j2
@@ -0,0 +1,13 @@
+# Netbox
+NETBOX_VERSION={{ container_netbox_version }}
+NETBOX_SECRET_KEY={{ container_netbox_secret_key }}
+NETBOX_DOMAIN={{ container_netbox_domain }}
+
+# Redis
+NETBOX_REDIS_VERSION={{ container_netbox_redis_version }}
+
+# PostgreSQL
+POSTGRES_VERSION={{ container_netbox_postgres_version }}
+POSTGRES_DB= {{ container_netbox_postgres_db }}
+POSTGRES_USER={{ container_netbox_postgres_user }}
+POSTGRES_PASSWORD={{ container_netbox_postgres_password }}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_netbox/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_netbox/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..5701baa
--- /dev/null
+++ b/ansible/roles/deploy_container_netbox/templates/docker-compose.yml.j2
@@ -0,0 +1,96 @@
+---
+services:
+ postgres:
+ image: postgres:${POSTGRES_VERSION:-16}
+ container_name: netbox-db
+ restart: unless-stopped
+ networks:
+ - netbox
+ volumes:
+ - netbox-db:/var/lib/postgresql/data
+ - ./data/backup:/backup # Volume for Cronjob: 0 2 * * * /usr/bin/docker exec netbox-db /bin/bash -c 'PGPASSWORD=changeMeNow! pg_dump --username=netbox netbox > >
+ environment:
+ POSTGRES_DB: ${POSTGRES_DB}
+ POSTGRES_USER: ${POSTGRES_USER}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ healthcheck:
+ test: ["CMD-SHELL", "sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+
+ redis:
+ image: redis:${NETBOX_REDIS_VERSION:-7}
+ restart: unless-stopped
+ networks:
+ - netbox
+ volumes:
+ - netbox-redis:/data
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 10s
+ timeout: 3s
+ retries: 5
+
+ netbox:
+ build:
+ context: .
+ dockerfile: Dockerfile-Plugins
+ args:
+ NETBOX_VERSION: ${NETBOX_VERSION:-latest}
+ container_name: netbox
+ restart: unless-stopped
+ networks:
+ - traefik
+ - netbox
+ env_file: .env
+ environment:
+ DB_NAME: ${POSTGRES_DB}
+ DB_USER: ${POSTGRES_USER}
+ DB_PASSWORD: ${POSTGRES_PASSWORD}
+ DB_HOST: postgres
+ REDIS_HOST: redis
+ REDIS_DATABASE: 0
+ REDIS_CACHE_DATABASE: 1
+ SECRET_KEY: ${NETBOX_SECRET_KEY}
+ ALLOWED_HOSTS: "*"
+ volumes:
+ - netbox-static:/opt/netbox/netbox/static
+ - netbox-media:/etc/netbox/media
+ - ./data/configuration/plugins.py:/etc/netbox/config/plugins.py
+ - ./data/netbox/static/img:/opt/netbox/netbox/static/netbox_topology_views/img
+ - ./data/netbox/static/js:/opt/netbox/netbox/static/netbox_topology_views/js
+ - ./data/netbox/static/css:/opt/netbox/netbox/static/netbox_topology_views/css
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.netbox.entrypoints=http"
+ - "traefik.http.routers.netbox.rule=Host(`${NETBOX_DOMAIN:?error}`)"
+ - "traefik.http.middlewares.netbox-https-redirect.redirectscheme.scheme=https"
+ - "traefik.http.routers.netbox.middlewares=traefik-https-redirect"
+ - "traefik.http.routers.netbox-secure.entrypoints=https"
+ - "traefik.http.routers.netbox-secure.rule=Host(`${NETBOX_DOMAIN:?error}`)"
+ - "traefik.http.routers.netbox-secure.tls=true"
+ - "traefik.http.services.netbox.loadbalancer.server.port=8080"
+ - "traefik.docker.network=traefik"
+ depends_on:
+ postgres:
+ condition: service_healthy
+ redis:
+ condition: service_healthy
+ healthcheck:
+ start_period: 60s
+ timeout: 3s
+ interval: 15s
+ test: "curl -f http://localhost:8080/login/ || exit 1"
+
+networks:
+ traefik:
+ external: true
+ netbox:
+ driver: bridge
+
+volumes:
+ netbox-db:
+ netbox-redis:
+ netbox-static:
+ netbox-media:
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_netbox/vars/main.yml b/ansible/roles/deploy_container_netbox/vars/main.yml
new file mode 100644
index 0000000..ade3d90
--- /dev/null
+++ b/ansible/roles/deploy_container_netbox/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir: /opt/docker/netbox
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_plausible/defaults/main.yml b/ansible/roles/deploy_container_plausible/defaults/main.yml
new file mode 100644
index 0000000..37f34fc
--- /dev/null
+++ b/ansible/roles/deploy_container_plausible/defaults/main.yml
@@ -0,0 +1,8 @@
+#############
+# Plausible #
+#############
+container_plausible_version: "latest"
+container_plausible_clickhouse_version: "latest"
+container_plausible_postgres_version: "latest"
+container_plausible_secret_key: "randon_secret_key" # openssl rand -base64 48
+container_plausible_domain: "plausible.example.com"
diff --git a/ansible/roles/deploy_container_plausible/files/disable-internal-memory-tracker.xml b/ansible/roles/deploy_container_plausible/files/disable-internal-memory-tracker.xml
new file mode 100644
index 0000000..b005e0a
--- /dev/null
+++ b/ansible/roles/deploy_container_plausible/files/disable-internal-memory-tracker.xml
@@ -0,0 +1,3 @@
+
+ true
+
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_plausible/files/ipv4-only.xml b/ansible/roles/deploy_container_plausible/files/ipv4-only.xml
new file mode 100644
index 0000000..e4f4b22
--- /dev/null
+++ b/ansible/roles/deploy_container_plausible/files/ipv4-only.xml
@@ -0,0 +1,3 @@
+
+ 0.0.0.0
+
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_plausible/files/logs.xml b/ansible/roles/deploy_container_plausible/files/logs.xml
new file mode 100644
index 0000000..3ffd303
--- /dev/null
+++ b/ansible/roles/deploy_container_plausible/files/logs.xml
@@ -0,0 +1,28 @@
+
+
+ warning
+ true
+
+
+
+ system
+
+ 7500
+
+ ENGINE = MergeTree
+ PARTITION BY event_date
+ ORDER BY (event_time)
+ TTL event_date + interval 30 day
+ SETTINGS ttl_only_drop_parts=1
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_plausible/files/low-resources.xml b/ansible/roles/deploy_container_plausible/files/low-resources.xml
new file mode 100644
index 0000000..aab3cd8
--- /dev/null
+++ b/ansible/roles/deploy_container_plausible/files/low-resources.xml
@@ -0,0 +1,23 @@
+
+
+
+ 524288000
+
+
+
+
+ 1
+
+ 8192
+
+ 1
+
+ 0
+
+ 0
+
+
+
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_plausible/handlers/main.yml b/ansible/roles/deploy_container_plausible/handlers/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_plausible/meta/main.yml b/ansible/roles/deploy_container_plausible/meta/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_plausible/tasks/main.yml b/ansible/roles/deploy_container_plausible/tasks/main.yml
new file mode 100644
index 0000000..8950db1
--- /dev/null
+++ b/ansible/roles/deploy_container_plausible/tasks/main.yml
@@ -0,0 +1,41 @@
+---
+- name: Ensure data directories exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/data/{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - "clickhouse"
+
+- name: Copy config files
+ ansible.builtin.copy:
+ src: "{{ item.src }}"
+ dest: "{{ container_base_dir }}/data/{{ item.dest }}"
+ mode: '0644'
+ loop:
+ - { src: 'ipv4-only.xml', dest: '/clickhouse/ipv4-only.xml' }
+ - { src: 'logs.xml', dest: '/clickhouse/logs.xml' }
+ - { src: 'low-resources.xml', dest: '/clickhouse/low-resources.xml' }
+ - { src: 'disable-internal-memory-tracker.xml', dest: '/clickhouse/disable-internal-memory-tracker.xml' }
+ become: false
+
+- name: Render Docker Compose and config files
+ ansible.builtin.template:
+ src: "{{ item.src }}"
+ dest: "{{ container_base_dir }}/{{ item.dest }}"
+ mode: '0644'
+ loop:
+ - { src: 'docker-compose.yml.j2', dest: 'docker-compose.yml' }
+ - { src: '.env.j2', dest: '.env' }
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
diff --git a/ansible/roles/deploy_container_plausible/templates/.env.j2 b/ansible/roles/deploy_container_plausible/templates/.env.j2
new file mode 100644
index 0000000..be0775b
--- /dev/null
+++ b/ansible/roles/deploy_container_plausible/templates/.env.j2
@@ -0,0 +1,5 @@
+PLAUSIBLE_VERSION={{ container_plausible_version }}
+PLAUSIBLE_DOMAIN={{ container_plausible_domain }}
+PLAUSIBLE_SECRET_KEY={{ container_plausible_secret_key }}
+POSTGRES_VERSION={{ container_plausible_postgres_version }}
+CLICKHOUSE_VERSION={{ container_plausible_clickhouse_version }}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_plausible/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_plausible/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..2d373bc
--- /dev/null
+++ b/ansible/roles/deploy_container_plausible/templates/docker-compose.yml.j2
@@ -0,0 +1,127 @@
+---
+services:
+ plausible_db:
+ image: postgres:${POSTGRES_VERSION}
+ container_name: plausible-db
+ restart: always
+ networks:
+ - plausible
+ volumes:
+ - db-data:/var/lib/postgresql/data
+ environment:
+ - POSTGRES_PASSWORD=postgres
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
+ start_period: 1m
+
+ plausible_events_db:
+ image: clickhouse/clickhouse-server:${CLICKHOUSE_VERSION}
+ restart: always
+ container_name: plausible-events
+ networks:
+ - plausible
+ volumes:
+ - event-data:/var/lib/clickhouse
+ - event-logs:/var/log/clickhouse-server
+ - ./data/clickhouse/logs.xml:/etc/clickhouse-server/config.d/logs.xml:ro
+ - ./data/clickhouse/disable-internal-memory-tracker.xml:/etc/clickhouse-server/config.d/disable-internal-memory-tracker.xml:ro
+ # This makes ClickHouse bind to IPv4 only, since Docker doesn't enable IPv6 in bridge networks by default.
+ # Fixes "Listen [::]:9000 failed: Address family for hostname not supported" warnings.
+ - ./data/clickhouse/ipv4-only.xml:/etc/clickhouse-server/config.d/ipv4-only.xml:ro
+ # This makes ClickHouse consume less resources, which is useful for small setups.
+ # https://clickhouse.com/docs/en/operations/tips#using-less-than-16gb-of-ram
+ - ./data/clickhouse/low-resources.xml:/etc/clickhouse-server/config.d/low-resources.xml:ro
+ ulimits:
+ nofile:
+ soft: 262144
+ hard: 262144
+ healthcheck:
+ test: ["CMD-SHELL", "wget --no-verbose --tries=1 -O - http://127.0.0.1:8123/ping || exit 1"]
+ start_period: 1m
+
+ plausible:
+ image: ghcr.io/plausible/community-edition:${PLAUSIBLE_VERSION}
+ container_name: plausible
+ restart: always
+ command: sh -c "/entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
+ depends_on:
+ plausible_db:
+ condition: service_healthy
+ plausible_events_db:
+ condition: service_healthy
+ networks:
+ - traefik
+ - plausible
+ volumes:
+ - plausible:/var/lib/plausible
+ ulimits:
+ nofile:
+ soft: 65535
+ hard: 65535
+ environment:
+ - TMPDIR=/var/lib/plausible/tmp
+ # required: https://github.com/plausible/community-edition/wiki/configuration#required
+ - BASE_URL=https://${PLAUSIBLE_DOMAIN}
+ - SECRET_KEY_BASE=${PLAUSIBLE_SECRET_KEY}
+ # optional: https://github.com/plausible/community-edition/wiki/configuration#optional
+ # registration: https://github.com/plausible/community-edition/wiki/configuration#registration
+ - TOTP_VAULT_KEY
+ - DISABLE_REGISTRATION
+ - ENABLE_EMAIL_VERIFICATION
+ # web: https://github.com/plausible/community-edition/wiki/configuration#web
+ - PHX_SERVER=true
+ - HTTP_PORT=8000
+ - HTTPS_PORT
+ - PORT=8000
+ # databases: https://github.com/plausible/community-edition/wiki/configuration#database
+ - DATABASE_URL
+ - CLICKHOUSE_DATABASE_URL
+ # Google: https://github.com/plausible/community-edition/wiki/configuration#google
+ - GOOGLE_CLIENT_ID
+ - GOOGLE_CLIENT_SECRET
+ # geolocation: https://github.com/plausible/community-edition/wiki/configuration#ip-geolocation
+ - IP_GEOLOCATION_DB
+ - GEONAMES_SOURCE_FILE
+ - MAXMIND_LICENSE_KEY
+ - MAXMIND_EDITION
+ # email: https://github.com/plausible/community-edition/wiki/configuration#email
+ - MAILER_ADAPTER
+ - MAILER_EMAIL
+ - MAILER_NAME
+ - SMTP_HOST_ADDR
+ - SMTP_HOST_PORT
+ - SMTP_USER_NAME
+ - SMTP_USER_PWD
+ - SMTP_HOST_SSL_ENABLED
+ - POSTMARK_API_KEY
+ - MAILGUN_API_KEY
+ - MAILGUN_DOMAIN
+ - MAILGUN_BASE_URI
+ - MANDRILL_API_KEY
+ - SENDGRID_API_KEY
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.plausible.entrypoints=https"
+ - "traefik.http.routers.plausible.rule=Host(`${PLAUSIBLE_DOMAIN}`)"
+ - "traefik.http.routers.plausible.tls=true"
+ - "traefik.http.routers.plausible.tls.certresolver=letsencrypt"
+ - "traefik.http.services.plausible.loadbalancer.server.port=8000"
+ - "traefik.docker.network=traefik"
+ healthcheck:
+ test: ["CMD-SHELL", "wget -q -T 5 -O - http://127.0.0.1:8000/ >/dev/null 2>&1 || exit 1"]
+ interval: 30s
+ timeout: 10s
+ retries: 2
+ start_period: 60s
+
+networks:
+ traefik:
+ external: true
+ plausible:
+ driver: bridge
+
+volumes:
+ db-data:
+ event-data:
+ event-logs:
+ plausible:
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_plausible/vars/main.yml b/ansible/roles/deploy_container_plausible/vars/main.yml
new file mode 100644
index 0000000..ce84700
--- /dev/null
+++ b/ansible/roles/deploy_container_plausible/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir: /opt/docker/plausible
diff --git a/ansible/roles/deploy_container_stirlingpdf/README.md b/ansible/roles/deploy_container_stirlingpdf/README.md
new file mode 100644
index 0000000..bf9f40d
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/README.md
@@ -0,0 +1,58 @@
+# Role: deploy_container_traefik
+
+## Purpose
+This role deploys and configures a StirlingPDF Container with Docker Compose
+
+## Variables
+
+### Default Variables (`defaults/main.yml`)
+```yaml
+container_stirlingpdf_version: latest
+container_stirlingpdf_app_name: StirlingPDF
+container_stirlingpdf_app_description: Your PDF Source
+container_stirlingpdf_domain: stirlingpdf.example.com
+
+```
+
+### Static Variables (`vars/main.yml`)
+```yaml
+container_base_dir: /opt/docker/stirlingpdf
+```
+
+### Role Usage
+
+```yaml
+- name: Deploy StirlingPDF container
+ hosts: docker
+ roles:
+ - role: deploy_container_stirlingpdf
+```
+
+## Requirements
+
+* Linux system (tested on Debian)
+* Docker Engine
+* Docker Compose v2 plugin (`docker compose` CLI)
+* Ansible 2.11 or higher
+* `community.docker` collection
+
+Install the required collection:
+
+```bash
+ansible-galaxy collection install community.docker
+```
+
+Or via `requirements.yml`:
+
+```yaml
+collections:
+ - name: community.docker
+ version: ">=3.4.0"
+```
+
+## Authors
+
+* Kevin Heyer
+ 📧 [kevin.heyer@wira-gmbh.de](mailto:kevin.heyer@wira-gmbh.de)
+
+```
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_stirlingpdf/defaults/main.yml b/ansible/roles/deploy_container_stirlingpdf/defaults/main.yml
new file mode 100644
index 0000000..e6ccb63
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/defaults/main.yml
@@ -0,0 +1,4 @@
+container_stirlingpdf_version: latest
+container_stirlingpdf_app_name: StirlingPDF
+container_stirlingpdf_app_description: Your PDF Source
+container_stirlingpdf_domain: stirlingpdf.example.com
diff --git a/ansible/roles/deploy_container_stirlingpdf/files/footer.html b/ansible/roles/deploy_container_stirlingpdf/files/footer.html
new file mode 100644
index 0000000..4cdd468
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/files/footer.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_stirlingpdf/files/navbar.html b/ansible/roles/deploy_container_stirlingpdf/files/navbar.html
new file mode 100644
index 0000000..c4874f7
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/files/navbar.html
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Account Settings
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_stirlingpdf/handlers/main.yml b/ansible/roles/deploy_container_stirlingpdf/handlers/main.yml
new file mode 100644
index 0000000..73b314f
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/handlers/main.yml
@@ -0,0 +1 @@
+---
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_stirlingpdf/meta/main.yml b/ansible/roles/deploy_container_stirlingpdf/meta/main.yml
new file mode 100644
index 0000000..66f2c52
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/meta/main.yml
@@ -0,0 +1,21 @@
+galaxy_info:
+ role_name: container_stirlingpdf
+ author: Kevin Heyer
+ description: |
+ Deploys and configures a StirlingPDF Container in a Docker.
+ license: MIT
+ min_ansible_version: "2.11"
+ platforms:
+ - name: Debian
+ versions:
+ - bookworm
+ - bullseye
+
+ galaxy_tags:
+ - docker
+ - pdf
+ - stirlingpdf
+ - container
+ - ansible
+
+dependencies: []
diff --git a/ansible/roles/deploy_container_stirlingpdf/tasks/main.yml b/ansible/roles/deploy_container_stirlingpdf/tasks/main.yml
new file mode 100644
index 0000000..f9a15e6
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/tasks/main.yml
@@ -0,0 +1,42 @@
+---
+- name: Ensure data directories exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/data/{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - "trainingData"
+ - "extraConfigs"
+ - "customFiles/templates/fragments"
+ - "logs"
+ - "pipeline"
+
+- name: Copy custom HTML files
+ ansible.builtin.copy:
+ src: '{{ item.src }}'
+ dest: '{{ item.dest }}'
+ mode: '0755'
+ loop:
+ - { src: navbar.html, dest: '{{ container_base_dir }}/data/customFiles/templates/fragments/navbar.html' }
+ - { src: footer.html, dest: '{{ container_base_dir }}/data/customFiles/templates/fragments/footer.html' }
+
+- name: Deploy Docker Compose and .env files
+ ansible.builtin.template:
+ src: "{{ item.src }}"
+ dest: "{{ container_base_dir }}/{{ item.dest }}"
+ mode: '0644'
+ loop:
+ - { src: 'docker-compose.yml.j2', dest: 'docker-compose.yml' }
+ - { src: '.env.j2', dest: '.env' }
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
diff --git a/ansible/roles/deploy_container_stirlingpdf/templates/.env.j2 b/ansible/roles/deploy_container_stirlingpdf/templates/.env.j2
new file mode 100644
index 0000000..3c62355
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/templates/.env.j2
@@ -0,0 +1,11 @@
+# Version of the StirlingPDF container
+STIRLINGPDF_VERSION={{ container_stirlingpdf_version}}
+
+# Application name (UI)
+STIRLINGPDF_APPNAME={{ container_stirlingpdf_app_name }}
+
+# Application description (UI)
+STIRLINGPDF_DESCRIPTION={{ container_stirlingpdf_app_description }}
+
+# Domain for the StirlingPDF service (replace this with the actual domain)
+STIRLINGPDF_DOMAIN={{ container_stirlingpdf_domain }}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_stirlingpdf/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_stirlingpdf/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..b3998b7
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/templates/docker-compose.yml.j2
@@ -0,0 +1,47 @@
+---
+services:
+ stirling-pdf:
+ image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:${STIRLINGPDF_VERSION}
+ container_name: stirlingpdf
+ networks:
+ - traefik
+ volumes:
+ - ./data/trainingData:/usr/share/tessdata # Required for extra OCR languages
+ - ./data/extraConfigs:/configs
+ - ./data/customFiles:/customFiles/
+ - ./data/logs:/logs/
+ - ./data/pipeline:/pipeline/
+ environment:
+ - DOCKER_ENABLE_SECURITY=false
+ - LANGS=de_DE
+ - SYSTEM_DEFAULTLOCALE=de-DE
+ - INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
+ - UI_APPNAME=${STIRLINGPDF_APPNAME:-StirlingPDF}
+ - UI_HOMEDESCRIPTION=${STIRLINGPDF_DESCRIPTION:-Your PDF Source}
+ - UI_APPNAMENAVBAR=${STIRLINGPDF_APPNAME:-StirlingPDF}
+ - SYSTEM_CUSTOMHTMLFILES=true
+ - DISABLE_PIXEL=true
+ labels:
+ - "traefik.enable=true"
+ - "traefik.docker.network=traefik"
+ - "traefik.http.routers.stirlingpdf.entrypoints=http"
+ - "traefik.http.routers.stirlingpdf.rule=Host(`${STIRLINGPDF_DOMAIN:?error}`)"
+ - "traefik.http.middlewares.stirlingpdf-https-redirect.redirectscheme.scheme=https"
+ - "traefik.http.routers.stirlingpdf.middlewares=traefik-https-redirect"
+ - "traefik.http.routers.stirlingpdf-secure.entrypoints=https"
+ - "traefik.http.routers.stirlingpdf-secure.rule=Host(`${STIRLINGPDF_DOMAIN:?error}`)"
+ - "traefik.http.routers.stirlingpdf-secure.tls=true"
+ - "traefik.http.routers.stirlingpdf-secure.service=stirlingpdf"
+ - "traefik.http.services.stirlingpdf.loadbalancer.server.port=8080"
+{% if container_traefik_auth == 'sso' %}
+ - "traefik.http.routers.stirlingpdf-secure.middlewares=middlewares-authelia@file"
+{% endif %}
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
+ interval: 5s
+ timeout: 10s
+ retries: 16
+
+networks:
+ traefik:
+ external: true
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_stirlingpdf/vars/main.yml b/ansible/roles/deploy_container_stirlingpdf/vars/main.yml
new file mode 100644
index 0000000..c767edc
--- /dev/null
+++ b/ansible/roles/deploy_container_stirlingpdf/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir: /opt/docker/stirlingpdf
diff --git a/ansible/roles/deploy_container_ticky/defaults/main.yml b/ansible/roles/deploy_container_ticky/defaults/main.yml
new file mode 100644
index 0000000..bf753b5
--- /dev/null
+++ b/ansible/roles/deploy_container_ticky/defaults/main.yml
@@ -0,0 +1,8 @@
+#########
+# Ticky #
+#########
+container_ticky_version: latest
+contaner_ticky_domain: kanban.example.com
+container_ticky_mysql_version: 8
+container_ticky_mysql_username: ticky
+container_ticky_mysql_password: random_password
diff --git a/ansible/roles/deploy_container_ticky/files/.gitkeep b/ansible/roles/deploy_container_ticky/files/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_ticky/handlers/main.yml b/ansible/roles/deploy_container_ticky/handlers/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_ticky/meta/main.yml b/ansible/roles/deploy_container_ticky/meta/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_ticky/tasks/main.yml b/ansible/roles/deploy_container_ticky/tasks/main.yml
new file mode 100644
index 0000000..2910371
--- /dev/null
+++ b/ansible/roles/deploy_container_ticky/tasks/main.yml
@@ -0,0 +1,30 @@
+---
+- name: Ensure data directories exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/data/{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - "upload"
+ - "mysql"
+
+- name: Deploy Docker Compose and .env files
+ ansible.builtin.template:
+ src: "{{ item.src }}"
+ dest: "{{ container_base_dir }}/{{ item.dest }}"
+ mode: '0644'
+ loop:
+ - { src: 'docker-compose.yml.j2', dest: 'docker-compose.yml' }
+ - { src: '.env.j2', dest: '.env' }
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
diff --git a/ansible/roles/deploy_container_ticky/templates/.env.j2 b/ansible/roles/deploy_container_ticky/templates/.env.j2
new file mode 100644
index 0000000..c262849
--- /dev/null
+++ b/ansible/roles/deploy_container_ticky/templates/.env.j2
@@ -0,0 +1,6 @@
+TICKY_VERSION={{ container_ticky_version }}
+TICKY_DOMAIN={{ contaner_ticky_domain }}
+
+MYSQL_VERSION={{ container_ticky_mysql_version }}
+MYSQL_USERNAME={{ container_ticky_mysql_username }}
+MYSQL_PASSWORD={{ container_ticky_mysql_password }}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_ticky/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_ticky/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..8ef645c
--- /dev/null
+++ b/ansible/roles/deploy_container_ticky/templates/docker-compose.yml.j2
@@ -0,0 +1,70 @@
+---
+services:
+ ticky:
+ image: ghcr.io/dkorecko/ticky:${TICKY_VERSION}
+ container_name: ticky
+ restart: unless-stopped
+ networks:
+ - traefik
+ - ticky
+ volumes:
+ - ./data/upload:/app/wwwroot/uploaded
+ environment:
+ - DB_HOST=ticky-db
+ - DB_NAME=ticky
+ - DB_USERNAME=${MYSQL_USERNAME}
+ - DB_PASSWORD=${MYSQL_PASSWORD}
+ - FULLY_OFFLINE=true
+ - DISABLE_USER_SIGNUPS=true
+ - SMTP_ENABLED=false
+ - SMTP_HOST=your-smtp-host
+ - SMTP_PORT=your-smtp-port
+ - SMTP_DISPLAY_NAME=Ticky
+ - SMTP_EMAIL=your-email@example.com
+ - SMTP_USERNAME=your-smtp-username
+ - SMTP_PASSWORD=your-smtp-password
+ - SMTP_SECURITY=true
+ labels:
+ - "traefik.enable=true"
+ - "traefik.docker.network=traefik"
+ - "traefik.http.routers.ticky.entrypoints=http"
+ - "traefik.http.routers.ticky.rule=Host(`${TICKY_DOMAIN}`)"
+ - "traefik.http.middlewares.ticky-https-redirect.redirectscheme.scheme=https"
+ - "traefik.http.routers.ticky.middlewares=traefik-https-redirect"
+ - "traefik.http.routers.ticky-secure.entrypoints=https"
+ - "traefik.http.routers.ticky-secure.rule=Host(`${TICKY_DOMAIN}`)"
+ - "traefik.http.routers.ticky-secure.tls=true"
+ - "traefik.http.services.ticky.loadbalancer.server.port=8080"
+ depends_on:
+ ticky-db:
+ condition: service_healthy
+ healthcheck:
+ test: ["CMD-SHELL", "curl --fail --silent --max-time 5 http://localhost:8080/ || exit 1"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 30s
+
+ ticky-db:
+ image: mysql:${MYSQL_VERSION}
+ container_name: ticky-db
+ restart: unless-stopped
+ networks:
+ - ticky
+ environment:
+ MYSQL_DATABASE: ticky
+ MYSQL_USER: ${MYSQL_USERNAME}
+ MYSQL_RANDOM_ROOT_PASSWORD: true
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD}
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
+ timeout: 2s
+ retries: 30
+ volumes:
+ - ./data/mysql:/var/lib/mysql
+
+networks:
+ traefik:
+ external: true
+ ticky:
+ driver: bridge
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_ticky/vars/main.yml b/ansible/roles/deploy_container_ticky/vars/main.yml
new file mode 100644
index 0000000..f86a2b9
--- /dev/null
+++ b/ansible/roles/deploy_container_ticky/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir: /opt/docker/ticky
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/README.md b/ansible/roles/deploy_container_traefik_with_letsencrypt/README.md
new file mode 100644
index 0000000..85d7fa8
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/README.md
@@ -0,0 +1,112 @@
+# Role: deploy_container_traefik
+
+## Purpose
+This role deploys and configures a Traefik reverse proxy using Docker Compose.
+It supports TLS, host-specific certificates, and renders all configuration files using Jinja2 templates.
+The configuration includes a fallback router/service to ensure safe defaults.
+
+## Variables
+
+### Default Variables (`defaults/main.yml`)
+```yaml
+
+container_traefik_create_network: true
+container_traefik_subnet: 192.168.222.0/24
+container_traefik_gateway: 192.168.222.1
+
+container_traefik_url: "traefik.example.com"
+container_traefik_version: "latest"
+
+# Create with: echo $(htpasswd -nB user) | sed -e s/\\$/\\$\\$/g
+container_traefik_basicuser: "admin"
+container_traefik_basicpassword: "changeme"
+
+container_traefik_http_port: 80
+container_traefik_https_port: 443
+
+# Dummy router/service to avoid template errors if nothing is defined
+container_traefik_routers:
+ dummy:
+ entryPoints: ["https"]
+ rule: "Host(`dummy.local`)"
+ service: dummy
+ tls: true
+
+container_traefik_services:
+ dummy:
+ loadBalancer:
+ servers:
+ - url: "https://127.0.0.1:443"
+ passHostHeader: true
+```
+
+### Static Variables (`vars/main.yml`)
+```yaml
+container_traefik_base_dir: /opt/docker/traefik
+```
+
+### Role Usage
+
+```yaml
+- name: Deploy Traefik container
+ hosts: traefik
+ roles:
+ - role: container_traefik
+ vars:
+ container_traefik_url: "traefik.example.com"
+ container_traefik_basicuser: "admin"
+ container_traefik_basicpassword: "$2y$05$"
+```
+
+## Requirements
+
+* Linux system (tested on Debian)
+* Docker Engine
+* Docker Compose v2 plugin (`docker compose` CLI)
+* Ansible 2.11 or higher
+* `community.docker` collection
+
+Install the required collection:
+
+```bash
+ansible-galaxy collection install community.docker
+```
+
+Or via `requirements.yml`:
+
+```yaml
+collections:
+ - name: community.docker
+ version: ">=3.4.0"
+```
+
+## Host-Specific Certificates
+
+Location: `host_files//certs/`
+
+Required files:
+
+* `wildcard.crt`
+* `wildcard.key`
+
+## Handlers
+
+* `Stop traefik container`
+* `Start traefik container`
+
+## Rendered Templates
+
+| Template File | Description |
+| ------------------------- | ---------------------------------- |
+| `docker-compose.yml.j2` | Docker Compose definition |
+| `.env.j2` | Environment variable file |
+| `traefik.yml.j2` | Main Traefik config (static) |
+| `tls.yml.j2` | TLS certificate reference |
+| `routers_services.yml.j2` | Static routers and services config |
+
+## Authors
+
+* Kevin Heyer
+ 📧 [kevin.heyer@wira-gmbh.de](mailto:kevin.heyer@wira-gmbh.de)
+
+```
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/defaults/main.yml b/ansible/roles/deploy_container_traefik_with_letsencrypt/defaults/main.yml
new file mode 100644
index 0000000..bd2ef86
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/defaults/main.yml
@@ -0,0 +1,30 @@
+###########
+# Traefik #
+###########
+container_traefik_url: "traefik.example.com"
+container_traefik_version: "latest"
+container_traefik_basicuser: "admin"
+container_traefik_basicpassword: "changeme"
+container_traefik_http_port: 80
+container_traefik_https_port: 443
+container_traefik_auth: "basic" # Options: basic, sso
+container_traefik_dashboard_enabled: false
+container_traefik_letsencrypt_email: "mail@example.com"
+
+container_traefik_routers:
+ dummy:
+ entryPoints: ["https"]
+ rule: "Host(`dummy.local`)"
+ service: dummy
+ tls: true
+
+container_traefik_services:
+ dummy:
+ loadBalancer:
+ servers:
+ - url: "https://127.0.0.1:443"
+ passHostHeader: true
+
+container_traefik_create_network: true
+container_traefik_subnet: 192.168.222.0/24
+container_traefik_gateway: 192.168.222.1
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/files/middlewares.yml b/ansible/roles/deploy_container_traefik_with_letsencrypt/files/middlewares.yml
new file mode 100644
index 0000000..d910a21
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/files/middlewares.yml
@@ -0,0 +1,19 @@
+http:
+ middlewares:
+ https-redirect:
+ redirectScheme:
+ scheme: https
+
+ default-headers:
+ headers:
+ frameDeny: true
+ sslRedirect: true
+ browserXssFilter: true
+ contentTypeNosniff: true
+ forceSTSHeader: true
+ stsIncludeSubdomains: true
+ stsPreload: true
+ stsSeconds: 15552000
+ customFrameOptionsValue: "SAMEORIGIN"
+ customRequestHeaders:
+ X-Forwarded-Proto: "https"
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/handlers/main.yml b/ansible/roles/deploy_container_traefik_with_letsencrypt/handlers/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/meta/main.yml b/ansible/roles/deploy_container_traefik_with_letsencrypt/meta/main.yml
new file mode 100644
index 0000000..6ddaa3d
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/meta/main.yml
@@ -0,0 +1,24 @@
+galaxy_info:
+ role_name: container_traefik_with_letsencrypt
+ author: Kevin Heyer
+ description: |
+ Deploys and configures a Traefik container using Docker Compose.
+ Includes support for custom .env, TLS with LetsEncrypt, routers/services config, and optional network creation.
+ license: MIT
+ min_ansible_version: "2.11"
+ platforms:
+ - name: Debian
+ versions:
+ - bookworm
+ - bullseye
+
+ galaxy_tags:
+ - docker
+ - traefik
+ - reverseproxy
+ - container
+ - https
+ - router
+ - ansible
+
+dependencies: []
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/tasks/main.yml b/ansible/roles/deploy_container_traefik_with_letsencrypt/tasks/main.yml
new file mode 100644
index 0000000..cd0ef05
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/tasks/main.yml
@@ -0,0 +1,67 @@
+---
+- name: Ensure data directories exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/data/{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - "certs"
+ - "config.d"
+ - "logs"
+
+- name: Ensure log files exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/data/logs/{{ item }}"
+ state: touch
+ mode: '0644'
+ loop:
+ - "traefik.log"
+ - "access.log"
+
+- name: Ensure acme.json exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/data/certs/{{ item }}"
+ state: touch
+ mode: '0600'
+ loop:
+ - "acme.json"
+
+- name: Create Docker network "traefik"
+ community.docker.docker_network:
+ name: traefik
+ ipam_config:
+ - subnet: "{{ container_traefik_subnet }}"
+ gateway: "{{ container_traefik_gateway }}"
+ when: container_traefik_create_network | default(true) | bool
+
+- name: Copy config files
+ ansible.builtin.copy:
+ src: "{{ item.src }}"
+ dest: "{{ container_base_dir }}/data/{{ item.dest }}"
+ mode: '0644'
+ loop:
+ - { src: 'middlewares.yml', dest: 'config.d/middlewares.yml' }
+ become: false
+
+- name: Render Docker Compose and config files
+ ansible.builtin.template:
+ src: "{{ item.src }}"
+ dest: "{{ container_base_dir }}/{{ item.dest }}"
+ mode: '0644'
+ loop:
+ - { src: 'docker-compose.yml.j2', dest: 'docker-compose.yml' }
+ - { src: '.env.j2', dest: '.env' }
+ - { src: 'routers_services.yml.j2', dest: 'data/config.d/routers_services.yml' }
+ - { src: 'traefik.yml.j2', dest: 'data/traefik.yml' }
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/.env.j2 b/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/.env.j2
new file mode 100644
index 0000000..137f5db
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/.env.j2
@@ -0,0 +1,6 @@
+TRAEFIK_URL="{{ container_traefik_url }}"
+TRAEFIK_VERSION="{{ container_traefik_version | default('latest') }}"
+TRAEFIK_BASICAUTH_USER="{{ container_traefik_basicuser | default('user') }}"
+TRAEFIK_BASICAUTH_PASSWORD="{{ container_traefik_basicpassword | default('changeme') }}"
+TRAEFIK_HTTP_PORT="{{ container_traefik_http_port | default('80') }}"
+TRAEFIK_HTTPS_PORT="{{ container_traefik_https_port | default('443') }}"
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..85fef04
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/docker-compose.yml.j2
@@ -0,0 +1,56 @@
+---
+services:
+ traefik:
+ image: traefik:${TRAEFIK_VERSION}
+ container_name: traefik
+ restart: unless-stopped
+ security_opt:
+ - no-new-privileges:true
+ networks:
+ traefik:
+ ports:
+ - "${TRAEFIK_HTTP_PORT}:80"
+ - "${TRAEFIK_HTTPS_PORT}:443"
+ volumes:
+ - /etc/localtime:/etc/localtime:ro
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ - ./data/traefik.yml:/traefik.yml:ro
+ - ./data/config.d:/config.d:ro
+ - ./data/logs/traefik.log:/var/log/traefik.log
+ - ./data/logs/access.log:/var/log/access.log
+ - ./data/certs:/etc/certs:rw
+ labels:
+ # Basis
+ - "traefik.enable=true"
+ - "traefik.http.routers.traefik.entrypoints=http"
+ - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_URL}`)"
+
+ # Middlewares
+ - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
+ - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
+ - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
+
+ # HTTPS
+ - "traefik.http.routers.traefik-secure.entrypoints=https"
+ - "traefik.http.routers.traefik-secure.rule=Host(`${TRAEFIK_URL}`)"
+ - "traefik.http.routers.traefik-secure.tls=true"
+ - "traefik.http.routers.traefik-secure.service=api@internal"
+
+ # Auth
+{% if container_traefik_auth == 'sso' %}
+ - "traefik.http.routers.traefik-secure.middlewares=middlewares-authelia@file"
+{% elif container_traefik_auth == 'basic' or container_traefik_auth is not defined %}
+ - "traefik.http.routers.traefik-secure.middlewares=basic-auth"
+ - "traefik.http.middlewares.basic-auth.basicauth.users=${TRAEFIK_BASICAUTH_USER}:${TRAEFIK_BASICAUTH_PASSWORD}"
+{% endif %}
+
+ healthcheck:
+ test: ["CMD", "traefik", "healthcheck"]
+ interval: 30s
+ timeout: 5s
+ retries: 3
+ start_period: 10s
+
+networks:
+ traefik:
+ external: true
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/routers_services.yml.j2 b/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/routers_services.yml.j2
new file mode 100644
index 0000000..92dafbd
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/routers_services.yml.j2
@@ -0,0 +1,34 @@
+http:
+ routers:
+{% for router_name, router in container_traefik_routers.items() %}
+ {{ router_name }}:
+ entryPoints:
+{% for ep in router.entryPoints %}
+ - "{{ ep }}"
+{% endfor %}
+ rule: "{{ router.rule }}"
+{% if router.middlewares is defined and router.middlewares %}
+ middlewares:
+{% for m in router.middlewares %}
+ - "{{ m }}"
+{% endfor %}
+{% endif %}
+{% if router.tls is defined and router.tls %}
+ tls: {}
+{% endif %}
+ service: "{{ router.service }}"
+{% endfor %}
+
+ services:
+{% for service_name, service in container_traefik_services.items() %}
+ {{ service_name }}:
+ loadBalancer:
+ servers:
+{% for server in service.loadBalancer.servers %}
+ - url: "{{ server.url }}"
+{% endfor %}
+ passHostHeader: {{ service.loadBalancer.passHostHeader | default(true) | lower }}
+{% if service.loadBalancer.serversTransport is defined %}
+ serversTransport: "{{ service.loadBalancer.serversTransport }}"
+{% endif %}
+{% endfor %}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/traefik.yml.j2 b/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/traefik.yml.j2
new file mode 100644
index 0000000..ac1a4ad
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/templates/traefik.yml.j2
@@ -0,0 +1,35 @@
+api:
+ dashboard: {{ container_traefik_dashboard_enabled | lower }}
+ insecure: false
+
+log:
+ level: "INFO"
+ filePath: "/var/log/traefik.log"
+
+accessLog:
+ filePath: "/var/log/access.log"
+ bufferingSize: 50
+
+entryPoints:
+ http:
+ address: ":80"
+ https:
+ address: ":443"
+
+ping:
+ entryPoint: http
+
+providers:
+ docker:
+ endpoint: "unix:///var/run/docker.sock"
+ exposedByDefault: false
+ file:
+ directory: "/config.d/"
+ watch: true
+
+certificatesResolvers:
+ letsencrypt:
+ acme:
+ email: {{ container_traefik_letsencrypt_email }}
+ storage: /etc/certs/acme.json
+ tlsChallenge: {}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_traefik_with_letsencrypt/vars/main.yml b/ansible/roles/deploy_container_traefik_with_letsencrypt/vars/main.yml
new file mode 100644
index 0000000..0fcca69
--- /dev/null
+++ b/ansible/roles/deploy_container_traefik_with_letsencrypt/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir : "/opt/docker/traefik-letsencrypt"
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_vaultwarden/README.md b/ansible/roles/deploy_container_vaultwarden/README.md
new file mode 100644
index 0000000..8a837ab
--- /dev/null
+++ b/ansible/roles/deploy_container_vaultwarden/README.md
@@ -0,0 +1,55 @@
+# Role: deploy_container_vaultwarden
+
+## Purpose
+This role installs and configures Vaultwarden (a Bitwarden-compatible password manager) as a Docker container.
+It generates a `docker-compose.yml` and `.env` file based on the provided variables and integrates the container into an existing Traefik setup.
+
+The role focuses solely on deployment and basic configuration of Vaultwarden, including SMTP settings and the admin token.
+
+## Variables
+
+### Default Variables (`defaults/main.yml`)
+```yaml
+container_vaultwarden_version: latest # (type: string) Vaultwarden container version
+container_vaultwarden_domain: vaultwarden.example.com # (type: string) Domain name for Vaultwarden
+container_vaultwarden_admin_token: generated_vaultwarden_hash # (type: string) Argon2 hash for admin login
+container_vaultwarden_smtp_host: ip_of_your_smtp_server # (type: string) SMTP server hostname/IP
+container_vaultwarden_smtp_from: mail@example.com # (type: string) Sender email address for notifications
+container_vaultwarden_smtp_port: 587 # (type: int) SMTP port (587 = TLS, 465 = SSL)
+container_vaultwarden_smtp_security: force_tls # (type: string) SMTP security ("force_tls", "starttls", "off")
+container_vaultwarden_smtp_username: your_smtp_username # (type: string) SMTP username
+container_vaultwarden_smtp_password: your_smtp_password # (type: string) SMTP password
+```
+Note: The admin token must be generated with
+```
+docker run --rm -it vaultwarden/server /vaultwarden hash
+```
+
+### Static Variables (`vars/main.yml`)
+```yaml
+container_base_dir: /opt/docker/vaultwarden
+```
+
+### Role Usage
+
+```yaml
+roles:
+ - role: deploy_container_vaultwarden
+ vars:
+ container_vaultwarden_domain: vault.yourdomain.tld
+ container_vaultwarden_smtp_host: smtp.yourprovider.com
+ container_vaultwarden_smtp_port: 465
+ container_vaultwarden_smtp_security: force_tls
+```
+
+## Requirements
+* Docker and Docker Compose must be installed
+* The Traefik network (traefik) must exist
+* Ansible access to the target system
+* Root/sudo privileges (become: true)
+
+## Authors
+* Author
+ 📧 [Kevin Heyer](mailto:kevin.heyer@wira-gmbh.de)
+
+```
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_vaultwarden/defaults/main.yml b/ansible/roles/deploy_container_vaultwarden/defaults/main.yml
new file mode 100644
index 0000000..39371ea
--- /dev/null
+++ b/ansible/roles/deploy_container_vaultwarden/defaults/main.yml
@@ -0,0 +1,16 @@
+###############
+# Vaultwarden #
+###############
+container_vaultwarden_version: latest
+container_vaultwarden_domain: vaultwarden.example.com
+
+# docker run --rm -it vaultwarden/server /vaultwarden hash
+# you need to escape all five occurrences of the dollar sign $ in the generated argon2 PHC string
+container_vaultwarden_admin_token: generated_vaultwarden_hash
+
+container_vaultwarden_smtp_host: ip_of_your_smtp_server
+container_vaultwarden_smtp_from: mail@example.com
+container_vaultwarden_smtp_port: 587 # or 465 for SSL
+container_vaultwarden_smtp_security: force_tls # or starttls or off
+container_vaultwarden_smtp_username: your_smtp_username
+container_vaultwarden_smtp_password: your_smtp_password
diff --git a/ansible/roles/deploy_container_vaultwarden/files/.gitkeep b/ansible/roles/deploy_container_vaultwarden/files/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_vaultwarden/handlers/main.yml b/ansible/roles/deploy_container_vaultwarden/handlers/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_vaultwarden/meta/main.yml b/ansible/roles/deploy_container_vaultwarden/meta/main.yml
new file mode 100644
index 0000000..e69de29
diff --git a/ansible/roles/deploy_container_vaultwarden/tasks/main.yml b/ansible/roles/deploy_container_vaultwarden/tasks/main.yml
new file mode 100644
index 0000000..c829d46
--- /dev/null
+++ b/ansible/roles/deploy_container_vaultwarden/tasks/main.yml
@@ -0,0 +1,30 @@
+---
+- name: Ensure data directories exist
+ ansible.builtin.file:
+ path: "{{ container_base_dir }}/data/{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - "vaultwarden"
+ - "attachments"
+
+- name: Deploy Docker Compose and .env files
+ ansible.builtin.template:
+ src: "{{ item.src }}"
+ dest: "{{ container_base_dir }}/{{ item.dest }}"
+ mode: '0644'
+ loop:
+ - { src: 'docker-compose.yml.j2', dest: 'docker-compose.yml' }
+ - { src: '.env.j2', dest: '.env' }
+
+- name: Stop Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: absent
+
+- name: Start Container
+ community.docker.docker_compose_v2:
+ project_src: "{{ container_base_dir }}"
+ state: present
+ pull: always
+ recreate: always
diff --git a/ansible/roles/deploy_container_vaultwarden/templates/.env.j2 b/ansible/roles/deploy_container_vaultwarden/templates/.env.j2
new file mode 100644
index 0000000..d50d123
--- /dev/null
+++ b/ansible/roles/deploy_container_vaultwarden/templates/.env.j2
@@ -0,0 +1,9 @@
+VAULTWARDEN_VERSION={{ container_vaultwarden_version }}
+VAULTWARDEN_DOMAIN={{ container_vaultwarden_domain }}
+VAULTWARDEN_ADMIN_TOKEN={{ container_vaultwarden_admin_token }}
+VAULTWARDEN_SMTP_HOST={{ container_vaultwarden_smtp_host }}
+VAULTWARDEN_SMTP_FROM={{ container_vaultwarden_smtp_from }}
+VAULTWARDEN_SMTP_PORT={{ container_vaultwarden_smtp_port }}
+VAULTWARDEN_SMTP_SECURITY={{ container_vaultwarden_smtp_security }}
+VAULTWARDEN_SMTP_USERNAME={{ container_vaultwarden_smtp_username }}
+VAULTWARDEN_SMTP_PASSWORD={{ container_vaultwarden_smtp_password}}
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_vaultwarden/templates/docker-compose.yml.j2 b/ansible/roles/deploy_container_vaultwarden/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..6b250c8
--- /dev/null
+++ b/ansible/roles/deploy_container_vaultwarden/templates/docker-compose.yml.j2
@@ -0,0 +1,42 @@
+---
+services:
+ vaultwarden:
+ image: ghcr.io/dani-garcia/vaultwarden:${VAULTWARDEN_VERSION}
+ container_name: vaultwarden
+ restart: always
+ volumes:
+ - './data/vaultwarden/:/data/'
+ - './data/attachments/:/attachments/'
+ networks:
+ - traefik
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.vaultwarden.entrypoints=http"
+ - "traefik.http.routers.vaultwarden.rule=Host(`${VAULTWARDEN_DOMAIN}`)"
+ - "traefik.http.middlewares.vaultwarden-https-redirect.redirectscheme.scheme=https"
+ - "traefik.http.routers.vaultwarden.middlewares=traefik-https-redirect"
+ - "traefik.http.routers.vaultwarden-secure.entrypoints=https"
+ - "traefik.http.routers.vaultwarden-secure.rule=Host(`${VAULTWARDEN_DOMAIN}`)"
+ - "traefik.http.routers.vaultwarden-secure.tls=true"
+ - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
+ - "traefik.docker.network=traefik"
+ environment:
+ DOMAIN: "https://${VAULTWARDEN_DOMAIN}"
+ #SIGNUPS_ALLOWED: false
+ #INVITATIONS_ALLOWED: false
+ ADMIN_TOKEN: ${VAULTWARDEN_ADMIN_TOKEN}
+ SMTP_HOST: ${VAULTWARDEN_SMTP_HOST}
+ SMTP_FROM: ${VAULTWARDEN_SMTP_FROM}
+ SMTP_PORT: ${VAULTWARDEN_SMTP_PORT}
+ SMTP_SECURITY: ${VAULTWARDEN_SMTP_SECURITY}
+ SMTP_USERNAME: ${VAULTWARDEN_SMTP_USERNAME}
+ SMTP_PASSWORD: ${VAULTWARDEN_SMTP_PASSWORD}
+ #LOG_FILE: /data/vaultwarden.log
+ #LOG_LEVEL: warn
+ EXTENDED_LOGGING: true
+ SIGNUPS_VERIFY: false
+ EXPERIMENTAL_CLIENT_FEATURE_FLAGS: fido2-vault-credentials,ssh-key-vault-item,ssh-agent
+
+networks:
+ traefik:
+ external: true
\ No newline at end of file
diff --git a/ansible/roles/deploy_container_vaultwarden/vars/main.yml b/ansible/roles/deploy_container_vaultwarden/vars/main.yml
new file mode 100644
index 0000000..52fd9f9
--- /dev/null
+++ b/ansible/roles/deploy_container_vaultwarden/vars/main.yml
@@ -0,0 +1 @@
+container_base_dir: /opt/docker/vaultwarden
\ No newline at end of file