mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2024-11-22 12:41:05 +01:00
Compare commits
No commits in common. "83cc40b7187f8f11b1f6d653fababa934baf7567" and "2e3e54f11f2eb3c11279c652587e66de2edea9f5" have entirely different histories.
83cc40b718
...
2e3e54f11f
@ -32,7 +32,7 @@ This software allows to setup the docker following applications:
|
||||
This software shipts the following tools which are natively setup on the server:
|
||||
- [Backups Cleanup](./roles/cleanup-backups-timer/README.md) - Cleans up old backups
|
||||
- [Btrfs Health Check](./roles/health-btrfs/README.md) - Checks the health of Btrfs file systems
|
||||
- [Docker Health Check](./roles/health-docker-container/) - Checks the health of docker containers
|
||||
- [Docker Health Check](./roles/health-docker/) - Checks the health of docker containers
|
||||
- [Docker Reverse Proxy](./roles/docker-reverse-proxy/README.md) - Docker Reverse Proxy Solution
|
||||
- [Docker Volume Backup](./roles/backup-docker-to-local/) - Backup Solution for Docker Volumes
|
||||
- [Pull Primary Backups](./roles/backup-remote-to-local/README.md) - Pulls the backups from another server and stores them
|
||||
|
@ -4,18 +4,17 @@
|
||||
|
||||
randomized_delay_sec: "15min"
|
||||
|
||||
on_calendar_health_btrfs: "*-*-* 00:00:00"
|
||||
on_calendar_health_journalctl: "*-*-* 00:00:00"
|
||||
on_calendar_health_disc_space: "*-*-* 06,12,18,00:00:00"
|
||||
on_calendar_health_docker_container: "*-*-* 09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,00,01,02:00:00"
|
||||
on_calendar_health_docker_volumes: "*-*-* 09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,00,01,02:15:00"
|
||||
on_calendar_health_nginx: "*-*-* 09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,00,01,02:45:00"
|
||||
on_calendar_btrfs_health_check: "*-*-* 00:00:00"
|
||||
on_calendar_journalctl_health_check: "*-*-* 00:00:00"
|
||||
on_calendar_disc_space_check: "*-*-* 06,12,18,00:00:00"
|
||||
on_calendar_docker_health_check: "*-*-* 09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,00,01,02:00:00"
|
||||
on_calendar_nginx_health_check: "*-*-* 09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,00,01,02:15:00"
|
||||
|
||||
on_calendar_cleanup_backups: "*-*-* 06,12,18,00:30:00"
|
||||
on_calendar_cleanup_disc_space: "*-*-* 07,13,19,01:30:00"
|
||||
on_calendar_backup_docker_to_local: "*-*-* 03:30:00"
|
||||
on_calendar_backup_remote_to_local: "*-*-* 21:30:00"
|
||||
on_calendar_heal_docker: "*-*-* 09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,00,01:30:00"
|
||||
on_calendar_backups_cleanup: "*-*-* 06,12,18,00:30:00"
|
||||
on_calendar_free_disc_space: "*-*-* 07,13,19,01:30:00"
|
||||
on_calendar_docker_volume_backup: "*-*-* 03:30:00"
|
||||
on_calendar_docker_compose_restart_unhealthy: "*-*-* 09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,00,01:30:00"
|
||||
on_calendar_pull_primary_backups: "*-*-* 21:30:00"
|
||||
on_calendar_renew_lets_encrypt_certificates: "*-*-* 12,00:30:00"
|
||||
on_calendar_deploy_mailu_certificates: "*-*-* 13,01:30:00"
|
||||
on_calendar_msi_keyboard_color: "*-*-* *:*:00"
|
||||
@ -23,7 +22,7 @@ on_calendar_msi_keyboard_color: "*-*-* *:*:00"
|
||||
# Space Variables
|
||||
size_percent_maximum_backup: 75
|
||||
size_percent_disc_space_warning: 85
|
||||
size_percent_cleanup_disc_space: 90
|
||||
size_percent_free_disc_space: 90
|
||||
|
||||
# Path Variables
|
||||
path_administrator_home: "/home/administrator/"
|
||||
|
16
playbook.yml
16
playbook.yml
@ -35,18 +35,18 @@
|
||||
- client-wireguard
|
||||
|
||||
# Native Webserver Roles
|
||||
- name: setup nginx-homepages
|
||||
hosts: nginx-homepage
|
||||
- name: setup homepages
|
||||
hosts: homepage
|
||||
become: true
|
||||
roles:
|
||||
- role: nginx-homepage
|
||||
- role: homepage
|
||||
vars:
|
||||
domain: "{{top_domain}}"
|
||||
- name: setup redirect hosts
|
||||
hosts: redirect
|
||||
become: true
|
||||
roles:
|
||||
- role: nginx-redirect
|
||||
- role: https-redirect
|
||||
vars:
|
||||
domain_mappings: "{{redirect_domain_mappings}}"
|
||||
|
||||
@ -190,14 +190,6 @@
|
||||
vars:
|
||||
domain: baserow.{{top_domain}}
|
||||
http_port: 8017
|
||||
- name: setup matomo hosts
|
||||
hosts: matomo
|
||||
become: true
|
||||
roles:
|
||||
- role: docker-matomo
|
||||
vars:
|
||||
domain: matomo.{{top_domain}}
|
||||
http_port: 8018
|
||||
- name: setup akaunting hosts
|
||||
hosts: akaunting
|
||||
become: true
|
||||
|
@ -8,7 +8,7 @@
|
||||
- name: pull backup-docker-to-local.git
|
||||
git:
|
||||
repo: "https://github.com/kevinveenbirkenbach/backup-docker-to-local.git"
|
||||
dest: "{{backup_docker_to_local_folder}}"
|
||||
dest: "{{docker_volume_backup_folder}}"
|
||||
update: yes
|
||||
register: git_result
|
||||
ignore_errors: true
|
||||
@ -30,9 +30,9 @@
|
||||
changed_when: backup_docker_to_local_timer.changed or activate_all_timers | default(false) | bool
|
||||
notify: restart backup-docker-to-local.timer
|
||||
|
||||
- name: create {{backup_docker_to_local_folder}}databases.csv
|
||||
- name: create {{docker_volume_backup_folder}}databases.csv
|
||||
copy:
|
||||
src: "{{ inventory_dir }}/files/{{ inventory_hostname }}{{backup_docker_to_local_folder}}databases.csv"
|
||||
dest: "{{backup_docker_to_local_folder}}databases.csv"
|
||||
src: "{{ inventory_dir }}/files/{{ inventory_hostname }}{{docker_volume_backup_folder}}databases.csv"
|
||||
dest: "{{docker_volume_backup_folder}}databases.csv"
|
||||
owner: root
|
||||
group: root
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service cleanup-failed-docker-backups.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/python {{backup_docker_to_local_folder}}backup-docker-to-local.py
|
||||
ExecStart=/usr/bin/python {{docker_volume_backup_folder}}backup-docker-to-local.py
|
@ -2,7 +2,7 @@
|
||||
Description=backups the docker volumes
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_backup_docker_to_local}}
|
||||
OnCalendar={{on_calendar_docker_volume_backup}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=false
|
||||
|
||||
|
@ -1 +1 @@
|
||||
backup_docker_to_local_folder: "{{path_administrator_scripts}}backup-docker-to-local/"
|
||||
docker_volume_backup_folder: "{{path_administrator_scripts}}backup-docker-to-local/"
|
@ -1,13 +1,13 @@
|
||||
- name: "create {{docker_backup_remote_to_local_folder}}"
|
||||
- name: "create {{docker_pull_primary_backups_folder}}"
|
||||
file:
|
||||
path: "{{docker_backup_remote_to_local_folder}}"
|
||||
path: "{{docker_pull_primary_backups_folder}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create backup-remote-to-local.sh
|
||||
copy:
|
||||
src: backup-remote-to-local.sh
|
||||
dest: "{{docker_backup_remote_to_local_folder}}backup-remote-to-local.sh"
|
||||
dest: "{{docker_pull_primary_backups_folder}}backup-remote-to-local.sh"
|
||||
mode: 0755
|
||||
|
||||
- name: create backup-remote-to-local.service
|
||||
@ -16,13 +16,13 @@
|
||||
|
||||
- name: create backup-remote-to-local.timer
|
||||
template: src=backup-remote-to-local.timer.j2 dest=/etc/systemd/system/backup-remote-to-local.timer
|
||||
register: backup_backup_remote_to_local_timer
|
||||
changed_when: backup_backup_remote_to_local_timer.changed or activate_all_timers | default(false) | bool
|
||||
register: backup_remote_to_local_timer
|
||||
changed_when: backup_remote_to_local_timer.changed or activate_all_timers | default(false) | bool
|
||||
notify: restart backup-remote-to-local timer
|
||||
|
||||
- name: create backup-remote-to-local.sh
|
||||
template:
|
||||
src: backup-remote-to-local.sh.j2
|
||||
dest: "{{docker_backup_remote_to_local_folder}}backup-remote-to-local.sh"
|
||||
dest: "{{docker_pull_primary_backups_folder}}backup-remote-to-local.sh"
|
||||
mode: 0755
|
||||
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service cleanup-failed-docker-backups.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/bash {{docker_backup_remote_to_local_folder}}backup-remote-to-local.sh
|
||||
ExecStart=/usr/bin/bash {{docker_pull_primary_backups_folder}}backup-remote-to-local.sh
|
||||
|
@ -3,6 +3,6 @@
|
||||
hosts="{{pull_remote_backups}}";
|
||||
errors=0
|
||||
for host in $hosts; do
|
||||
bash {{docker_backup_remote_to_local_folder}}backup-remote-to-local.sh $host || ((errors+=1));
|
||||
bash {{docker_pull_primary_backups_folder}}backup-remote-to-local.sh $host || ((errors+=1));
|
||||
done;
|
||||
exit $errors;
|
||||
|
@ -2,7 +2,7 @@
|
||||
Description=starts pull remote backup timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_backup_remote_to_local}}
|
||||
OnCalendar={{on_calendar_pull_primary_backups}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=false
|
||||
|
||||
|
@ -1 +1 @@
|
||||
docker_backup_remote_to_local_folder: "{{path_administrator_scripts}}pull-primary-backups/"
|
||||
docker_pull_primary_backups_folder: "{{path_administrator_scripts}}pull-primary-backups/"
|
@ -5,16 +5,16 @@
|
||||
- python-psutil
|
||||
state: present
|
||||
|
||||
- name: "create {{docker_cleanup_backups}}"
|
||||
- name: "create {{docker_backups_cleanup}}"
|
||||
file:
|
||||
path: "{{docker_cleanup_backups}}"
|
||||
path: "{{docker_backups_cleanup}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create cleanup-backups.py
|
||||
copy:
|
||||
src: "cleanup-backups.py"
|
||||
dest: "{{docker_cleanup_backups}}cleanup-backups.py"
|
||||
dest: "{{docker_backups_cleanup}}cleanup-backups.py"
|
||||
|
||||
- name: create cleanup-backups.service
|
||||
template:
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/python {{docker_cleanup_backups}}cleanup-backups.py --backups-folder-path {{backups_folder_path}} --maximum-backup-size-percent {{size_percent_maximum_backup}}
|
||||
ExecStart=/usr/bin/python {{docker_backups_cleanup}}cleanup-backups.py --backups-folder-path {{backups_folder_path}} --maximum-backup-size-percent {{size_percent_maximum_backup}}
|
||||
|
@ -1 +1 @@
|
||||
docker_cleanup_backups: "{{path_administrator_scripts}}cleanup-backups/"
|
||||
docker_backups_cleanup: "{{path_administrator_scripts}}cleanup-backups/"
|
@ -2,7 +2,7 @@
|
||||
Description=starts cleanup-backups.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_cleanup_backups}}
|
||||
OnCalendar={{on_calendar_backups_cleanup}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=true
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
- name: "create {{cleanup_disc_space_folder}}"
|
||||
- name: "create {{free_disc_space_folder}}"
|
||||
file:
|
||||
path: "{{cleanup_disc_space_folder}}"
|
||||
path: "{{free_disc_space_folder}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create cleanup-disc-space.sh
|
||||
template:
|
||||
src: cleanup-disc-space.sh.j2
|
||||
dest: "{{cleanup_disc_space_folder}}cleanup-disc-space.sh"
|
||||
dest: "{{free_disc_space_folder}}cleanup-disc-space.sh"
|
||||
|
||||
- name: create cleanup-disc-space.service
|
||||
template:
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash {{cleanup_disc_space_folder}}cleanup-disc-space.sh {{size_percent_cleanup_disc_space}}
|
||||
ExecStart=/bin/bash {{free_disc_space_folder}}cleanup-disc-space.sh {{size_percent_free_disc_space}}
|
@ -2,8 +2,8 @@
|
||||
# @param $1 mimimum free disc space
|
||||
# @param $2 --force to for execution indepentend on how much disc space is free
|
||||
|
||||
execute_cleanup_disc_space=0
|
||||
minimum_percent_cleanup_disc_space="$1"
|
||||
execute_free_disc_space=0
|
||||
minimum_percent_free_disc_space="$1"
|
||||
force_freeing=false
|
||||
echo "Checking free disc space..."
|
||||
df
|
||||
@ -14,12 +14,12 @@ fi
|
||||
for disc_use_percent in $(df --output=pcent | sed 1d)
|
||||
do
|
||||
disc_use_percent_number=$(echo "$disc_use_percent" | sed "s/%//")
|
||||
if [ "$disc_use_percent_number" -gt "$minimum_percent_cleanup_disc_space" ]; then
|
||||
if [ "$disc_use_percent_number" -gt "$minimum_percent_free_disc_space" ]; then
|
||||
echo "WARNING: $disc_use_percent_number exceeds the limit of {{size_percent_disc_space_warning}}%."
|
||||
execute_cleanup_disc_space+=1;
|
||||
execute_free_disc_space+=1;
|
||||
fi
|
||||
done
|
||||
if [ "$disc_use_percent_number" -gt "$minimum_percent_cleanup_disc_space" ] || [ "$force_freeing" = true ]; then
|
||||
if [ "$disc_use_percent_number" -gt "$minimum_percent_free_disc_space" ] || [ "$force_freeing" = true ]; then
|
||||
echo "cleaning up /tmp" &&
|
||||
find /tmp -type f -atime +10 -delete || exit 1
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
Description=starts cleanup-disc-space.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_cleanup_disc_space}}
|
||||
OnCalendar={{on_calendar_free_disc_space}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=true
|
||||
|
||||
|
@ -1 +1 @@
|
||||
cleanup_disc_space_folder: "{{path_administrator_scripts}}cleanup-disc-space/"
|
||||
free_disc_space_folder: "{{path_administrator_scripts}}cleanup-disc-space/"
|
@ -1,7 +1,7 @@
|
||||
- name: pull cleanup-failed-docker-backups.git
|
||||
git:
|
||||
repo: "https://github.com/kevinveenbirkenbach/cleanup-failed-docker-backups.git"
|
||||
dest: "{{backup_docker_to_local_cleanup_folder}}"
|
||||
dest: "{{docker_volume_backup_cleanup_folder}}"
|
||||
update: yes
|
||||
register: git_result
|
||||
ignore_errors: true
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/sh -c '/usr/bin/yes | /usr/bin/bash {{backup_docker_to_local_cleanup_folder}}cleanup.sh {{backup_docker_to_local_cleanup_machine_id}} {{backup_docker_to_local_cleanup_trigger_directory}}'
|
||||
ExecStart=/bin/sh -c '/usr/bin/yes | /usr/bin/bash {{docker_volume_backup_cleanup_folder}}cleanup.sh {{docker_volume_backup_cleanup_machine_id}} {{docker_volume_backup_cleanup_trigger_directory}}'
|
@ -1 +1 @@
|
||||
backup_docker_to_local_cleanup_folder: "{{path_administrator_scripts}}cleanup-failed-docker-backups/"
|
||||
docker_volume_backup_cleanup_folder: "{{path_administrator_scripts}}cleanup-failed-docker-backups/"
|
@ -1,32 +0,0 @@
|
||||
# Docker Matomo Role
|
||||
|
||||
This Ansible role deploys a Matomo analytics platform instance using Docker.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker and Docker-Compose installed on the host machine.
|
||||
- Nginx installed for reverse proxy configuration.
|
||||
- Certbot installed for SSL certificate generation.
|
||||
|
||||
## Role Variables
|
||||
|
||||
- `domain`: The domain where Matomo will be accessible.
|
||||
- `administrator_email`: The email used for SSL certificate registration.
|
||||
- `path_docker_compose_files`: Path to store Docker Compose files.
|
||||
- `http_port`: The host port that Matomo will be accessible on.
|
||||
- `matomo_database_password`: Password for the Matomo database.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `docker-reverse-proxy`: An Ansible role for configuring the reverse proxy.
|
||||
|
||||
## Example Playbook
|
||||
|
||||
```yaml
|
||||
- hosts: servers
|
||||
roles:
|
||||
- { role: docker-matomo, domain: 'example.com', http_port: 8080 }
|
||||
```
|
||||
|
||||
## AI Generated
|
||||
This script was created with the help of ChatGPT. The full conversation is [here](https://chat.openai.com/share/49e0c7e4-a2af-4a04-adad-7a735bdd85c4) available.
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
- name: recreate matomo
|
||||
command:
|
||||
cmd: docker-compose -p matomo up -d --force-recreate
|
||||
chdir: "{{path_docker_compose_files}}matomo/"
|
||||
environment:
|
||||
COMPOSE_HTTP_TIMEOUT: 600
|
||||
DOCKER_CLIENT_TIMEOUT: 600
|
@ -1,2 +0,0 @@
|
||||
dependencies:
|
||||
- docker-reverse-proxy
|
@ -1,21 +0,0 @@
|
||||
---
|
||||
- name: recieve {{domain}} certificate
|
||||
command: certbot certonly --agree-tos --email {{administrator_email}} --non-interactive --webroot -w /var/lib/letsencrypt/ -d {{domain}}
|
||||
|
||||
- name: configure {{domain}}.conf
|
||||
template:
|
||||
src: "roles/docker-reverse-proxy/templates/domain.conf.j2"
|
||||
dest: "/etc/nginx/conf.d/{{domain}}.conf"
|
||||
notify: restart nginx
|
||||
|
||||
- name: "create {{path_docker_compose_files}}matomo/"
|
||||
file:
|
||||
path: "{{path_docker_compose_files}}matomo/"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: add docker-compose.yml
|
||||
template:
|
||||
src: "docker-compose.yml.j2"
|
||||
dest: "{{path_docker_compose_files}}matomo/docker-compose.yml"
|
||||
notify: recreate matomo
|
@ -1,46 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
application:
|
||||
logging:
|
||||
driver: journald
|
||||
image: matomo
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:{{http_port}}:80"
|
||||
environment:
|
||||
MATOMO_DATABASE_HOST: "database:3306"
|
||||
MATOMO_DATABASE_ADAPTER: "mysql"
|
||||
MATOMO_DATABASE_USERNAME: "matomo"
|
||||
MATOMO_DATABASE_PASSWORD: "{{matomo_database_password}}"
|
||||
MATOMO_DATABASE_DBNAME: "matomo"
|
||||
links:
|
||||
- database
|
||||
depends_on:
|
||||
- database
|
||||
volumes:
|
||||
- data:/var/www/html
|
||||
database:
|
||||
logging:
|
||||
driver: journald
|
||||
image: mariadb
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_DATABASE: "matomo"
|
||||
MYSQL_USER: "matomo"
|
||||
MYSQL_PASSWORD: "{{matomo_database_password}}"
|
||||
MYSQL_ROOT_PASSWORD: "{{matomo_database_password}}"
|
||||
MARIADB_AUTO_UPGRADE: "1"
|
||||
volumes:
|
||||
- database:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: "/usr/bin/mariadb --user=matomo --password={{matomo_database_password}} --execute \"SHOW DATABASES;\""
|
||||
interval: 3s
|
||||
timeout: 1s
|
||||
retries: 5
|
||||
volumes:
|
||||
database:
|
||||
data:
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
@ -1,3 +1,3 @@
|
||||
dependencies:
|
||||
- docker
|
||||
- nginx-https
|
||||
- https-server
|
||||
|
@ -1,6 +1,5 @@
|
||||
dependencies:
|
||||
- backup-docker-to-local
|
||||
- user-administrator
|
||||
- health-docker-container
|
||||
- health-docker-volumes
|
||||
- health-docker
|
||||
- heal-docker
|
||||
|
@ -1,13 +1,13 @@
|
||||
- name: "create {{heal_docker}}"
|
||||
- name: "create {{docker_compose_restart_unhealthy}}"
|
||||
file:
|
||||
path: "{{heal_docker}}"
|
||||
path: "{{docker_compose_restart_unhealthy}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create heal-docker.py
|
||||
copy:
|
||||
src: heal-docker.py
|
||||
dest: "{{heal_docker}}heal-docker.py"
|
||||
dest: "{{docker_compose_restart_unhealthy}}heal-docker.py"
|
||||
|
||||
- name: create heal-docker.service
|
||||
template:
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/python {{heal_docker}}heal-docker.py
|
||||
ExecStart=/bin/python {{docker_compose_restart_unhealthy}}heal-docker.py
|
||||
|
@ -2,7 +2,7 @@
|
||||
Description=starts heal-docker.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_heal_docker}}
|
||||
OnCalendar={{on_calendar_docker_compose_restart_unhealthy}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=false
|
||||
|
||||
|
@ -1 +1 @@
|
||||
heal_docker: "{{path_administrator_scripts}}heal-docker/"
|
||||
docker_compose_restart_unhealthy: "{{path_administrator_scripts}}heal-docker/"
|
@ -1,13 +1,13 @@
|
||||
- name: "create {{docker_health_btrfs_folder}}"
|
||||
- name: "create {{docker_btrfs_health_check_folder}}"
|
||||
file:
|
||||
path: "{{docker_health_btrfs_folder}}"
|
||||
path: "{{docker_btrfs_health_check_folder}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create health-btrfs.sh
|
||||
copy:
|
||||
src: health-btrfs.sh
|
||||
dest: "{{docker_health_btrfs_folder}}health-btrfs.sh"
|
||||
dest: "{{docker_btrfs_health_check_folder}}health-btrfs.sh"
|
||||
|
||||
- name: create health-btrfs.service
|
||||
template:
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash {{docker_health_btrfs_folder}}health-btrfs.sh
|
||||
ExecStart=/bin/bash {{docker_btrfs_health_check_folder}}health-btrfs.sh
|
||||
|
@ -2,7 +2,7 @@
|
||||
Description=starts health-btrfs.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_health_btrfs}}
|
||||
OnCalendar={{on_calendar_btrfs_health_check}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=false
|
||||
|
||||
|
@ -1 +1 @@
|
||||
docker_health_btrfs_folder: "{{path_administrator_scripts}}health-btrfs/"
|
||||
docker_btrfs_health_check_folder: "{{path_administrator_scripts}}health-btrfs/"
|
@ -1,14 +1,14 @@
|
||||
#!/bin/sh
|
||||
# @param $1 mimimum free disc space
|
||||
errors=0
|
||||
minimum_percent_cleanup_disc_space="$1"
|
||||
minimum_percent_free_disc_space="$1"
|
||||
echo "checking disc space use..."
|
||||
df
|
||||
for disc_use_percent in $(df --output=pcent | sed 1d)
|
||||
do
|
||||
disc_use_percent_number=$(echo "$disc_use_percent" | sed "s/%//")
|
||||
if [ "$disc_use_percent_number" -gt "$minimum_percent_cleanup_disc_space" ]; then
|
||||
echo "WARNING: $disc_use_percent_number exceeds the limit of $minimum_percent_cleanup_disc_space%."
|
||||
if [ "$disc_use_percent_number" -gt "$minimum_percent_free_disc_space" ]; then
|
||||
echo "WARNING: $disc_use_percent_number exceeds the limit of $minimum_percent_free_disc_space%."
|
||||
errors+=1;
|
||||
fi
|
||||
done
|
||||
|
@ -1,13 +1,13 @@
|
||||
- name: "create {{health_disc_space_folder}}"
|
||||
- name: "create {{disc_space_check_folder}}"
|
||||
file:
|
||||
path: "{{health_disc_space_folder}}"
|
||||
path: "{{disc_space_check_folder}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create health-disc-space.sh
|
||||
copy:
|
||||
src: health-disc-space.sh
|
||||
dest: "{{health_disc_space_folder}}health-disc-space.sh"
|
||||
dest: "{{disc_space_check_folder}}health-disc-space.sh"
|
||||
|
||||
- name: create health-disc-space.service
|
||||
template:
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash {{health_disc_space_folder}}health-disc-space.sh {{size_percent_disc_space_warning}}
|
||||
ExecStart=/bin/bash {{disc_space_check_folder}}health-disc-space.sh {{size_percent_disc_space_warning}}
|
||||
|
@ -2,7 +2,7 @@
|
||||
Description=starts health-disc-space.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_health_disc_space}}
|
||||
OnCalendar={{on_calendar_disc_space_check}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=true
|
||||
|
||||
|
@ -1 +1 @@
|
||||
health_disc_space_folder: "{{path_administrator_scripts}}health-disc-space/"
|
||||
disc_space_check_folder: "{{path_administrator_scripts}}health-disc-space/"
|
@ -1,21 +0,0 @@
|
||||
# Health Check for Docker Containers
|
||||
|
||||
## Description
|
||||
|
||||
This Ansible role is designed to ensure the health of Docker containers running on a system. It includes a script that checks for unhealthy or exited Docker containers and sets up a systemd service and timer to regularly execute this check.
|
||||
|
||||
## Files
|
||||
|
||||
- `vars/main.yml`: Variable definitions for the script's directory.
|
||||
- `handlers/main.yml`: Handlers to reload and restart the systemd service and timer.
|
||||
- `files/health-docker-container.sh`: The script that checks the container health.
|
||||
- `tasks/main.yml`: Tasks to create necessary directories, copy scripts, and create systemd service and timer.
|
||||
- `templates/health-docker-container.service.j2`: Systemd service template.
|
||||
- `templates/health-docker-container.timer.j2`: Systemd timer template.
|
||||
- `meta/main.yml`: Meta information declaring dependencies for the role.
|
||||
|
||||
## Usage
|
||||
|
||||
To use this role, include it in your playbook and set the `path_administrator_scripts` variable to the desired path for the health check scripts.
|
||||
|
||||
Ensure that the `systemd_notifier` dependency is satisfied for error notifications.
|
@ -1,11 +0,0 @@
|
||||
- name: "reload health-docker-container.service"
|
||||
systemd:
|
||||
name: health-docker-container.service
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
- name: "restart health-docker-container.timer"
|
||||
systemd:
|
||||
name: health-docker-container.timer
|
||||
state: restarted
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
@ -1,22 +0,0 @@
|
||||
- name: "create {{health_docker_container_folder}}"
|
||||
file:
|
||||
path: "{{health_docker_container_folder}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create health-docker-container.sh
|
||||
copy:
|
||||
src: health-docker-container.sh
|
||||
dest: "{{health_docker_container_folder}}health-docker-container.sh"
|
||||
|
||||
- name: create health-docker-container.service
|
||||
template: src=health-docker-container.service.j2 dest=/etc/systemd/system/health-docker-container.service
|
||||
notify: reload health-docker-container.service
|
||||
|
||||
- name: create health-docker-container.timer
|
||||
template:
|
||||
src: health-docker-container.timer.j2
|
||||
dest: "/etc/systemd/system/health-docker-container.timer"
|
||||
register: health_docker_container_timer
|
||||
changed_when: health_docker_container_timer.changed or activate_all_timers | default(false) | bool
|
||||
notify: restart health-docker-container.timer
|
@ -1,10 +0,0 @@
|
||||
[Unit]
|
||||
Description=starts health-docker-container.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_health_docker_container}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=false
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
@ -1 +0,0 @@
|
||||
health_docker_container_folder: "{{path_administrator_scripts}}health-docker-container/"
|
@ -1,25 +0,0 @@
|
||||
# Health Check for Docker Volumes
|
||||
|
||||
## Description
|
||||
|
||||
This role checks for anonymous Docker volumes that are not bound to a container and may be left over from previous operations. It provides a cleanup mechanism by identifying such volumes, excluding any that are whitelisted, and possibly taking action against them.
|
||||
|
||||
## Files
|
||||
|
||||
- `vars/main.yml`: Variable definitions for the script's directory and whitelist.
|
||||
- `handlers/main.yml`: Handlers to reload and restart the systemd service and timer.
|
||||
- `files/health-docker-volumes.sh`: The script that checks for anonymous Docker volumes and excludes whitelisted volumes.
|
||||
- `tasks/main.yml`: Tasks to create necessary directories, copy scripts, and create systemd service and timer.
|
||||
- `templates/health-docker-volumes.service.j2`: Systemd service template, including the whitelisted volumes as a parameter.
|
||||
- `templates/health-docker-volumes.timer.j2`: Systemd timer template.
|
||||
- `meta/main.yml`: Meta information declaring dependencies for the role.
|
||||
|
||||
## Usage
|
||||
|
||||
Include this role in your playbook and set the `path_administrator_scripts` variable to determine where the health check scripts should reside. Define `whitelisted_anonymous_volumes` in `vars/main.yml` with an array of volume IDs that should be ignored by the health check.
|
||||
|
||||
Ensure that the `systemd_notifier` dependency is satisfied for error notifications.
|
||||
|
||||
## Created with AI
|
||||
This script was created with the help of AI. The full conversation you find [here](https://chat.openai.com/share/1fa829f1-f001-4111-b1d4-1b2e3d583da2).
|
||||
|
@ -1,44 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
status=0
|
||||
|
||||
# The first argument is a space-separated list of whitelisted volume IDs
|
||||
whitelist=$1
|
||||
whitelisted_volumes=($whitelist) # Split into an array
|
||||
|
||||
anonymous_volumes=$(docker volume ls --format "{{.Name}}" | grep -E '^[a-f0-9]{64}$')
|
||||
|
||||
if [ -z "$anonymous_volumes" ]; then
|
||||
echo "No anonymous volumes found."
|
||||
exit $status
|
||||
fi
|
||||
|
||||
echo "Anonymous volumes found:"
|
||||
|
||||
for volume in $anonymous_volumes; do
|
||||
# Check if the volume is in the whitelist
|
||||
if printf '%s\n' "${whitelisted_volumes[@]}" | grep -q "^$volume$"; then
|
||||
echo "Volume $volume is whitelisted and will be skipped."
|
||||
continue
|
||||
fi
|
||||
|
||||
status=1
|
||||
container_ids=$(docker ps -aq --filter volume=$volume)
|
||||
if [ -z "$container_ids" ]; then
|
||||
echo "Volume $volume is not used by any running containers."
|
||||
continue
|
||||
fi
|
||||
|
||||
for container_id in $container_ids; do
|
||||
container_name=$(docker inspect --format '{{ .Name }}' $container_id | sed 's#^/##')
|
||||
mount_path=$(docker inspect --format "{{ range .Mounts }}{{ if eq .Name \"$volume\" }}{{ .Destination }}{{ end }}{{ end }}" $container_id)
|
||||
|
||||
if [ -n "$mount_path" ]; then
|
||||
echo "Volume $volume is used by container $container_name at mount path $mount_path"
|
||||
else
|
||||
echo "Volume $volume is used by container $container_name, but mount path could not be determined."
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
exit $status
|
@ -1,11 +0,0 @@
|
||||
- name: "reload health-docker-volumes.service"
|
||||
systemd:
|
||||
name: health-docker-volumes.service
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
- name: "restart health-docker-volumes.timer"
|
||||
systemd:
|
||||
name: health-docker-volumes.timer
|
||||
state: restarted
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
@ -1,2 +0,0 @@
|
||||
dependencies:
|
||||
- systemd_notifier
|
@ -1,22 +0,0 @@
|
||||
- name: "create {{health_docker_volumes_folder}}"
|
||||
file:
|
||||
path: "{{health_docker_volumes_folder}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create health-docker-volumes.sh
|
||||
copy:
|
||||
src: health-docker-volumes.sh
|
||||
dest: "{{health_docker_volumes_folder}}health-docker-volumes.sh"
|
||||
|
||||
- name: create health-docker-volumes.service
|
||||
template: src=health-docker-volumes.service.j2 dest=/etc/systemd/system/health-docker-volumes.service
|
||||
notify: reload health-docker-volumes.service
|
||||
|
||||
- name: create health-docker-volumes.timer
|
||||
template:
|
||||
src: health-docker-volumes.timer.j2
|
||||
dest: "/etc/systemd/system/health-docker-volumes.timer"
|
||||
register: health_docker_volumes_timer
|
||||
changed_when: health_docker_volumes_timer.changed or activate_all_timers | default(false) | bool
|
||||
notify: restart health-docker-volumes.timer
|
@ -1,7 +0,0 @@
|
||||
[Unit]
|
||||
Description=Checking docker health
|
||||
OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash {{ health_docker_volumes_folder }}health-docker-volumes.sh "{{ whitelisted_anonymous_docker_volumes | join(' ') }}"
|
@ -1 +0,0 @@
|
||||
health_docker_volumes_folder: "{{path_administrator_scripts}}health-docker-volumes/"
|
11
roles/health-docker/handlers/main.yml
Normal file
11
roles/health-docker/handlers/main.yml
Normal file
@ -0,0 +1,11 @@
|
||||
- name: "reload health-docker.service"
|
||||
systemd:
|
||||
name: health-docker.service
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
- name: "restart health-docker.timer"
|
||||
systemd:
|
||||
name: health-docker.timer
|
||||
state: restarted
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
22
roles/health-docker/tasks/main.yml
Normal file
22
roles/health-docker/tasks/main.yml
Normal file
@ -0,0 +1,22 @@
|
||||
- name: "create {{docker_health_check_folder}}"
|
||||
file:
|
||||
path: "{{docker_health_check_folder}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create health-docker.sh
|
||||
copy:
|
||||
src: health-docker.sh
|
||||
dest: "{{docker_health_check_folder}}health-docker.sh"
|
||||
|
||||
- name: create health-docker.service
|
||||
template: src=health-docker.service.j2 dest=/etc/systemd/system/health-docker.service
|
||||
notify: reload health-docker.service
|
||||
|
||||
- name: create health-docker.timer
|
||||
template:
|
||||
src: health-docker.timer.j2
|
||||
dest: "/etc/systemd/system/health-docker.timer"
|
||||
register: health_docker_timer
|
||||
changed_when: health_docker_timer.changed or activate_all_timers | default(false) | bool
|
||||
notify: restart health-docker.timer
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash {{health_docker_container_folder}}health-docker-container.sh
|
||||
ExecStart=/bin/bash {{docker_health_check_folder}}health-docker.sh
|
@ -1,8 +1,8 @@
|
||||
[Unit]
|
||||
Description=starts health-docker-volumes.service
|
||||
Description=starts health-docker.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_health_docker_volumes}}
|
||||
OnCalendar={{on_calendar_docker_health_check}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=false
|
||||
|
1
roles/health-docker/vars/main.yml
Normal file
1
roles/health-docker/vars/main.yml
Normal file
@ -0,0 +1 @@
|
||||
docker_health_check_folder: "{{path_administrator_scripts}}health-docker/"
|
@ -1,13 +1,13 @@
|
||||
- name: "create {{health_journalctl_folder}}"
|
||||
- name: "create {{journalctl_health_check_folder}}"
|
||||
file:
|
||||
path: "{{health_journalctl_folder}}"
|
||||
path: "{{journalctl_health_check_folder}}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create health-journalctl.sh
|
||||
copy:
|
||||
src: health-journalctl.sh
|
||||
dest: "{{health_journalctl_folder}}health-journalctl.sh"
|
||||
dest: "{{journalctl_health_check_folder}}health-journalctl.sh"
|
||||
|
||||
- name: create health-journalctl.service
|
||||
template:
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash {{health_journalctl_folder}}health-journalctl.sh
|
||||
ExecStart=/bin/bash {{journalctl_health_check_folder}}health-journalctl.sh
|
||||
|
@ -2,7 +2,7 @@
|
||||
Description=starts health-journalctl.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{on_calendar_health_journalctl}}
|
||||
OnCalendar={{on_calendar_journalctl_health_check}}
|
||||
RandomizedDelaySec={{randomized_delay_sec}}
|
||||
Persistent=false
|
||||
|
||||
|
@ -1 +1 @@
|
||||
health_journalctl_folder: "{{path_administrator_scripts}}health-journalctl/"
|
||||
journalctl_health_check_folder: "{{path_administrator_scripts}}health-journalctl/"
|
@ -3,16 +3,16 @@
|
||||
name: python-requests
|
||||
state: present
|
||||
|
||||
- name: "create {{ health_nginx_folder }}"
|
||||
- name: "create {{ nginx_health_check_folder }}"
|
||||
file:
|
||||
path: "{{ health_nginx_folder }}"
|
||||
path: "{{ nginx_health_check_folder }}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: create health-nginx.py
|
||||
copy:
|
||||
src: health-nginx.py
|
||||
dest: "{{ health_nginx_folder }}health-nginx.py"
|
||||
dest: "{{ nginx_health_check_folder }}health-nginx.py"
|
||||
|
||||
- name: create health-nginx.service
|
||||
template:
|
||||
|
@ -4,4 +4,4 @@ OnFailure=systemd-notifier@%n.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/python3 {{ health_nginx_folder }}health-nginx.py
|
||||
ExecStart=/usr/bin/python3 {{ nginx_health_check_folder }}health-nginx.py
|
||||
|
@ -2,7 +2,7 @@
|
||||
Description=starts health-nginx.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{ on_calendar_health_nginx }}
|
||||
OnCalendar={{ on_calendar_nginx_health_check }}
|
||||
RandomizedDelaySec={{ randomized_delay_sec }}
|
||||
Persistent=false
|
||||
|
||||
|
@ -1 +1 @@
|
||||
health_nginx_folder: "{{ path_administrator_scripts }}health-nginx/"
|
||||
nginx_health_check_folder: "{{ path_administrator_scripts }}health-nginx/"
|
||||
|
@ -1,3 +1,3 @@
|
||||
dependencies:
|
||||
- nginx-https
|
||||
- https-server
|
||||
- git
|
@ -5,10 +5,10 @@
|
||||
- name: recieve {{domain}} certificate
|
||||
command: certbot certonly --agree-tos --email {{administrator_email}} --non-interactive --webroot -w /var/lib/letsencrypt/ -d {{domain}}
|
||||
|
||||
- name: nginx-homepage repo git
|
||||
- name: homepage repo git
|
||||
git:
|
||||
repo: "{{nginx_homepage_repository_address}}"
|
||||
dest: "{{nginx_homepage_root}}"
|
||||
repo: "{{homepage_repository_address}}"
|
||||
dest: "/usr/share/nginx/homepage"
|
||||
update: yes
|
||||
register: git_result
|
||||
ignore_errors: true
|
@ -9,7 +9,7 @@ server
|
||||
|
||||
location /
|
||||
{
|
||||
root {{nginx_homepage_root}};
|
||||
root /usr/share/nginx/homepage;
|
||||
index index.html index.htm;
|
||||
}
|
||||
}
|
2
roles/https-redirect/meta/main.yml
Normal file
2
roles/https-redirect/meta/main.yml
Normal file
@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
- https-server
|
5
roles/https-redirect/readme.md
Normal file
5
roles/https-redirect/readme.md
Normal file
@ -0,0 +1,5 @@
|
||||
# native https-redirect
|
||||
|
||||
## see
|
||||
- https://stackoverflow.com/questions/6045020/how-to-redirect-to-a-different-domain-using-nginx
|
||||
- https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html
|
@ -1,33 +0,0 @@
|
||||
# Nginx Homepage Role
|
||||
|
||||
This Ansible role configures an Nginx server to serve a static homepage. It handles domain configuration, SSL certificate retrieval with Let's Encrypt, and cloning the homepage content from a Git repository.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Ansible 2.9 or higher
|
||||
- Nginx installed on the target machine
|
||||
- Git installed on the target machine (if cloning a repo)
|
||||
- `nginx-https` and `git` roles available or configured if they are used as dependencies
|
||||
|
||||
## Role Variables
|
||||
|
||||
- `nginx_homepage_root`: The directory where the homepage content will be stored (default: `/usr/share/nginx/homepage`)
|
||||
- `domain`: The domain name for the Nginx server configuration
|
||||
- `administrator_email`: The email used for SSL certificate registration with Let's Encrypt
|
||||
- `nginx_homepage_repository_address`: The Git repository address containing the homepage content
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `nginx-https`: A role for setting up an HTTPS server
|
||||
- `git`: A role for installing Git
|
||||
|
||||
## Example Playbook
|
||||
|
||||
```yaml
|
||||
- hosts: servers
|
||||
roles:
|
||||
- { role: nginx-homepage, domain: 'example.com', administrator_email: 'admin@example.com' }
|
||||
```
|
||||
|
||||
## Author Information
|
||||
This role was created in 2023 by Kevin Veen Birkenbach.
|
@ -1 +0,0 @@
|
||||
nginx_homepage_root: /usr/share/nginx/homepage
|
@ -1,2 +0,0 @@
|
||||
dependencies:
|
||||
- nginx-https
|
@ -1,29 +0,0 @@
|
||||
# Nginx Redirect Role
|
||||
|
||||
This Ansible role configures Nginx to perform 301 redirects from one domain to another. It handles SSL certificate retrieval for the source domains and sets up the Nginx configuration to redirect to the specified target domains.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Ansible 2.9 or higher
|
||||
- Nginx installed on the target machine
|
||||
- Let's Encrypt for SSL certificate management
|
||||
|
||||
## Role Variables
|
||||
|
||||
- `domain_mappings`: A list of objects with `source` and `target` properties specifying the domains to redirect from and to.
|
||||
- `administrator_email`: The email used for SSL certificate registration with Let's Encrypt.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `nginx-https`: A role for setting up HTTPS for Nginx
|
||||
- `letsencrypt`: A role for managing SSL certificates with Let's Encrypt
|
||||
|
||||
## Example Playbook
|
||||
|
||||
```yaml
|
||||
- hosts: servers
|
||||
roles:
|
||||
- { role: nginx-redirect, domain_mappings: [ {source: 'example.com', target: 'newdomain.com'} ] }
|
||||
|
||||
## Author Information
|
||||
This role was created in 2023 by Kevin Veen Birkenbach.
|
Loading…
Reference in New Issue
Block a user