add new container

This commit is contained in:
= 2025-08-13 17:43:37 +02:00
parent e0436feaf3
commit bcc7fb55d5
92 changed files with 1996 additions and 0 deletions

View file

@ -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)
```

View file

@ -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"

View file

@ -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

View file

@ -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 }}

View file

@ -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:

View file

@ -0,0 +1 @@
container_base_dir : "/opt/docker/healthchecks"

View file

@ -0,0 +1,5 @@
############
# IT-Tools #
############
container_ittools_version: latest
container_ittools_domain: ittools.example.com

View file

@ -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

View file

@ -0,0 +1,2 @@
ITTOOLS_VERSION: {{ container_ittools_version }}
ITTOOLS_DOMAIN: {{ container_ittools_domain }}

View file

@ -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

View file

@ -0,0 +1 @@
container_base_dir: /opt/docker/ittools

View file

@ -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

View file

@ -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

View file

@ -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 }}

View file

@ -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

View file

@ -0,0 +1 @@
container_base_dir: /opt/docker/limesurvey

View file

@ -0,0 +1,5 @@
#############
# NetAlertX #
#############
container_netalertx_version: latest
container_netalertx_domain: netalertx.example.com

View file

@ -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

View file

@ -0,0 +1,2 @@
NETALERTX_VERSION={{ container_netalertx_version }}
NETALERTX_DOMAIN={{ container_netalertx_domain }}

View file

@ -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

View file

@ -0,0 +1 @@
container_base_dir: /opt/docker/netalertx

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,3 @@
netbox-plugin-dns
netbox-topology-views
netbox-qrcode

View file

@ -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
}
}
}

View file

@ -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

View file

@ -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 }}

View file

@ -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:

View file

@ -0,0 +1 @@
container_base_dir: /opt/docker/netbox

View file

@ -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"

View file

@ -0,0 +1,3 @@
<clickhouse>
<disable_internal_memory_tracker>true</disable_internal_memory_tracker>
</clickhouse>

View file

@ -0,0 +1,3 @@
<clickhouse>
<listen_host>0.0.0.0</listen_host>
</clickhouse>

View file

@ -0,0 +1,28 @@
<clickhouse>
<logger>
<level>warning</level>
<console>true</console>
</logger>
<query_log replace="1">
<database>system</database>
<table>query_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
<engine>
ENGINE = MergeTree
PARTITION BY event_date
ORDER BY (event_time)
TTL event_date + interval 30 day
SETTINGS ttl_only_drop_parts=1
</engine>
</query_log>
<!-- Stops unnecessary logging -->
<metric_log remove="remove" />
<asynchronous_metric_log remove="remove" />
<query_thread_log remove="remove" />
<text_log remove="remove" />
<trace_log remove="remove" />
<session_log remove="remove" />
<part_log remove="remove" />
</clickhouse>

View file

@ -0,0 +1,23 @@
<!-- https://clickhouse.com/docs/en/operations/tips#using-less-than-16gb-of-ram -->
<clickhouse>
<!--
https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings#mark_cache_size -->
<mark_cache_size>524288000</mark_cache_size>
<profile>
<default>
<!-- https://clickhouse.com/docs/en/operations/settings/settings#max_threads -->
<max_threads>1</max_threads>
<!-- https://clickhouse.com/docs/en/operations/settings/settings#max_block_size -->
<max_block_size>8192</max_block_size>
<!-- https://clickhouse.com/docs/en/operations/settings/settings#max_download_threads -->
<max_download_threads>1</max_download_threads>
<!--
https://clickhouse.com/docs/en/operations/settings/settings#input_format_parallel_parsing -->
<input_format_parallel_parsing>0</input_format_parallel_parsing>
<!--
https://clickhouse.com/docs/en/operations/settings/settings#output_format_parallel_formatting -->
<output_format_parallel_formatting>0</output_format_parallel_formatting>
</default>
</profile>
</clickhouse>

View file

@ -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

View file

@ -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 }}

View file

@ -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:

View file

@ -0,0 +1 @@
container_base_dir: /opt/docker/plausible

View file

@ -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)
```

View file

@ -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

View file

@ -0,0 +1,6 @@
<footer th:fragment="footer" id="footer" class="text-center">
<script type="module" th:src="@{'/js/thirdParty/cookieconsent-config.js'}"></script>
<div class="footer-center">
</div>
</footer>

View file

@ -0,0 +1,277 @@
<div th:fragment="navbar" class="mx-auto" style="position: sticky; top:0; z-index:10000; width:100%">
<script th:src="@{'/js/languageSelection.js'}"></script>
<script th:src="@{'/js/navbar.js'}"></script>
<script th:src="@{'/js/additionalLanguageCode.js'}"></script>
<script th:inline="javascript">
// Initializing the scripts
initLanguageSettings();
document.addEventListener('DOMContentLoaded', function () {
toolsManager();
});
</script>
<script th:inline="javascript">
const currentVersion = /*[[${@appVersion}]]*/ '';
const noFavourites = /*[[#{noFavourites}]]*/ '';
console.log(noFavourites);
const updateAvailable = /*[[#{settings.updateAvailable}]]*/ '';
</script>
<script th:src="@{'/js/homecard.js'}"></script>
<script th:src="@{'/js/githubVersion.js'}"></script>
<form th:action="@{'/dummyFormToPopulateCSRF'}" method="post" enctype="multipart/form-data"></form>
<nav class="navbar navbar-expand-xl" style="
background: var(--md-nav-background);
border-bottom-style: solid;
border-bottom-width: 1px;
border-color: var(--md-nav-color-on-separator)">
<div class="container " style="max-width: 100%;">
<a class="navbar-brand" th:href="${@contextPath}" style="display: flex;">
<img class="main-icon" th:src="@{'/favicon.svg'}" alt="icon">
<span class="icon-text" th:text="${@navBarText}"></span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="material-symbols-rounded">
menu
</span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto flex-nowrap">
<!-- All Tools -->
<li id="navItemToHide" class="nav-item dropdown dropdown-mega position-static">
<a class="nav-link" id="navbarDropdown-1" href="#" role="button" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<span class="material-symbols-rounded">
apps
</span>
<span class="icon-text" th:data-text="#{navbar.allTools}" th:text="#{navbar.allTools}"></span>
</a>
<div class="dropdown-menu dropdown-menu-tp" aria-labelledby="navbarDropdown-1">
<div class="dropdown-menu-wrapper" style="justify-content: center; display:flex">
<div class="feature-rows">
<th:block th:insert="~{fragments/navElements.html :: navElements}"></th:block>
</div>
</div>
</div>
</li>
<li class="nav-item" th:if="${@endpointConfiguration.isEndpointEnabled('multi-tool')}">
<a class="nav-link" href="#" th:href="@{'/multi-tool'}"
th:classappend="${currentPage}=='multi-tool' ? 'active' : ''" th:title="#{home.multiTool.desc}">
<span class="material-symbols-rounded">
construction
</span>
<span class="icon-text" th:data-text="#{navbar.multiTool}" th:text="#{navbar.multiTool}"></span>
</a>
</li>
<li class="nav-item" th:if="${@endpointConfiguration.isEndpointEnabled('pipeline')}">
<a class="nav-link" href="#" th:href="@{'/pipeline'}"
th:classappend="${currentPage}=='pipeline' ? 'active' : ''" th:title="#{home.pipeline.desc}">
<span class="material-symbols-rounded">
family_history
</span>
<span class="icon-text" th:data-text="#{home.pipeline.title}" th:text="#{home.pipeline.title}"></span>
</a>
</li>
<li class="nav-item" th:if="${@endpointConfiguration.isEndpointEnabled('compress-pdf')}">
<a class="nav-link" href="#" title="#{home.compressPdfs.title}" th:href="@{'/compress-pdf'}"
th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''" th:title="#{home.compressPdfs.desc}">
<span class="material-symbols-rounded">
zoom_in_map
</span>
<span class="icon-text" th:data-text="#{home.compressPdfs.title}"
th:text="#{home.compressPdfs.title}"></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{'/view-pdf'}"
th:classappend="${currentPage}=='view-pdf' ? 'active' : ''" th:title="#{home.viewPdf.desc}">
<span class="material-symbols-rounded">
menu_book
</span>
<span class="icon-text" th:data-text="#{home.viewPdf.title}" th:text="#{home.viewPdf.title}"></span>
</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link" href="#" th:href="@{'/merge-pdfs'}"
th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''" th:title="#{home.merge.desc}">
<span class="material-symbols-rounded">
add_to_photos
</span>
<span class="icon-text" th:data-text="#{home.merge.title}" th:text="#{home.merge.title}"></span>
</a>
</li> -->
</ul>
<ul class="navbar-nav flex-nowrap">
<li class="nav-item dropdown">
<a class="nav-link" id="navbarDropdown-5" href="#" role="button" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false" th:title="#{navbar.favorite}">
<span class="material-symbols-rounded">
star
</span>
<span class="icon-text icon-hide" th:data-text="#{navbar.favorite}" th:text="#{navbar.favorite}"></span>
</a>
<div class="dropdown-menu dropdown-menu-tp dropdown-mw-28" aria-labelledby="navbarDropdown-5">
<div class="dropdown-menu-wrapper px-xl-2 px-2" id="favoritesDropdown">
<!-- Dropdown items will be added here by JavaScript -->
</div>
</div>
</li>
<li class="nav-item">
<a class="nav-link" id="dark-mode-toggle" href="#" th:title="#{navbar.darkmode}">
<span class="material-symbols-rounded" id="dark-mode-icon">
dark_mode
</span>
<span class="icon-text icon-hide" id="dark-mode-text" th:data-text="#{navbar.darkmode}"
th:text="#{navbar.darkmode}"></span>
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false" th:title="#{navbar.language}">
<span class="material-symbols-rounded">
language
</span>
<span class="icon-text icon-hide" th:data-text="#{navbar.language}" th:text="#{navbar.language}"></span>
</a>
<div class="dropdown-menu dropdown-menu-tp" aria-labelledby="languageDropdown">
<div class="dropdown-menu-wrapper px-xl-2 px-2">
<div id="languageSelection" class="scrollable-y lang_dropdown-mw scalable-languages-container">
<th:block th:insert="~{fragments/languages :: langs}"></th:block>
</div>
</div>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link" href="#" id="searchDropdown" role="button" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false" th:title="#{navbar.search}">
<span class="material-symbols-rounded">
search
</span>
<span class="icon-text icon-hide">Search</span>
</a>
<div class="dropdown-menu dropdown-menu-tp" aria-labelledby="searchDropdown">
<div class="dropdown-menu-wrapper px-xl-2 px-2">
<form th:action="@{''}" class="d-flex p-2 search-form" id="searchForm">
<input class="form-control search-input" type="search" th:placeholder="#{navbar.search}"
aria-label="Search" id="navbarSearchInput">
</form>
<!-- Search Results -->
<div id="searchResults" class="search-results scrollable-y dropdown-mw-20"></div>
</div>
</div>
</li>
<li class="nav-item">
<!-- Settings Button -->
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#settingsModal"
th:title="#{navbar.settings}">
<span class="material-symbols-rounded">
settings
</span>
<span class="icon-text icon-hide" th:data-text="#{navbar.settings}" th:text="#{navbar.settings}"></span>
</a>
</li>
</ul>
</div>
</div>
<script th:src="@{'/js/favourites.js'}"></script>
<script th:src="@{'/js/search.js'}"></script>
</nav>
<th:block th:insert="~{fragments/errorBannerPerPage.html :: errorBannerPerPage}"></th:block>
<div class="modal fade" id="settingsModal" tabindex="-1" role="dialog" aria-labelledby="settingsModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="settingsModalLabel" th:text="#{settings.title}"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">
close
</span>
</button>
</div>
<div class="modal-body">
<p class="mb-0" th:utext="#{settings.appVersion} + ' ' + ${@appVersion}"></p>
<div class="d-flex justify-content-between align-items-center mb-3 mt-3">
<div class="footer-center" style="flex-direction: row;">
<a href="https://github.com/Stirling-Tools/Stirling-PDF" class="mx-1" role="button" target="_blank"
th:title="#{visitGithub}">
<img th:src="@{'/images/github.svg'}" alt="github">
</a>
<a href="https://hub.docker.com/r/stirlingtools/stirling-pdf" class="mx-1" role="button" target="_blank"
th:title="#{seeDockerHub}">
<img th:src="@{'/images/docker.svg'}" alt="docker">
</a>
<a href="https://discord.gg/Cn8pWhQRxZ" class="mx-1" role="button" target="_blank"
th:title="#{joinDiscord}">
<img th:src="@{'/images/discord.svg'}" alt="discord">
</a>
</div>
<a th:href="@{'/swagger-ui/index.html'}" class="btn btn-sm btn-outline-primary mx-1" role="button"
target="_blank">API</a>
<a href="https://github.com/Stirling-Tools/Stirling-PDF/releases"
class="btn btn-sm btn-outline-primary mx-1" id="update-btn" th:utext="#{settings.update}" role="button"
target="_blank"></a>
</div>
<div class="mb-3">
<label for="downloadOption" th:utext="#{settings.downloadOption.title}"></label>
<select class="form-control" id="downloadOption">
<option value="sameWindow" th:utext="#{settings.downloadOption.1}"></option>
<option value="newWindow" th:utext="#{settings.downloadOption.2}"></option>
<option value="downloadFile" th:utext="#{settings.downloadOption.3}"></option>
</select>
</div>
<div class="mb-3">
<label for="zipThreshold" th:utext="#{settings.zipThreshold}"></label><br />
<input type="range" class="form-range" min="1" max="9" step="1" id="zipThreshold" value="4">
<span id="zipThresholdValue" class="ms-2"></span>
</div>
<div class="form-check mb-3">
<input type="checkbox" id="boredWaiting" th:title="#{settings.bored.help}">
<label for="boredWaiting" th:text="#{bored}"></label>
</div>
<div class="form-check mb-3">
<input type="checkbox" id="cacheInputs" th:title="#{settings.cacheInputs.help}">
<label for="cacheInputs" th:text="#{settings.cacheInputs.name}"></label>
</div>
<a th:if="${@loginEnabled and @activeSecurity}" th:href="@{'/account'}" class="btn btn-sm btn-outline-primary"
role="button" th:text="#{settings.accountSettings}" target="_blank">Account Settings</a>
</div>
<div class="modal-footer">
<a th:if="${@loginEnabled and @activeSecurity}" class="btn btn-danger" role="button"
th:text="#{settings.signOut}" th:href="@{'/logout'}">Sign Out</a>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
</div>
</div>
</div>
</div>
<script th:src="@{'/js/settings.js'}"></script>
<script th:inline="javascript">
window.onload = function () {
updateFavoritesDropdown();
}
document.addEventListener('DOMContentLoaded', function () {
const navbarLink = document.querySelector(".navbar-brand");
const contentPath = /*[[${@contextPath}]]*/ '';
if (localStorage.getItem("defaultView") === "home-legacy") {
navbarLink.setAttribute("href", contentPath + "home-legacy");
} else {
navbarLink.setAttribute("href", contentPath);
}
});
</script>
</div>

View file

@ -0,0 +1 @@
---

View file

@ -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: []

View file

@ -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

View file

@ -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 }}

View file

@ -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

View file

@ -0,0 +1 @@
container_base_dir: /opt/docker/stirlingpdf

View file

@ -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

View file

@ -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

View file

@ -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 }}

View file

@ -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

View file

@ -0,0 +1 @@
container_base_dir: /opt/docker/ticky

View file

@ -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$<bcrypt_hash>"
```
## 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/<inventory_hostname>/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)
```

View file

@ -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

View file

@ -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"

View file

@ -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: []

View file

@ -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

View file

@ -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') }}"

View file

@ -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

View file

@ -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 %}

View file

@ -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: {}

View file

@ -0,0 +1 @@
container_base_dir : "/opt/docker/traefik-letsencrypt"

View file

@ -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)
```

View file

@ -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

View file

@ -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

View file

@ -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}}

View file

@ -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

View file

@ -0,0 +1 @@
container_base_dir: /opt/docker/vaultwarden