mirror of
https://github.com/cwchristerw/tjas-infra
synced 2025-09-20 11:32:53 +00:00
1189 lines
36 KiB
YAML
1189 lines
36 KiB
YAML
---
|
|
- name: "Deployer - SSH - Add Authorized Keys"
|
|
ansible.builtin.template:
|
|
src: './files/ssh/authorized_keys'
|
|
dest: '/root/.ssh/authorized_keys'
|
|
tags:
|
|
- ssh
|
|
|
|
- name: "Deployer - SSH - Config"
|
|
ansible.builtin.template:
|
|
src: './files/ssh/sshd_config'
|
|
dest: '/etc/ssh/sshd_config'
|
|
register: deployerTaskS1
|
|
tags:
|
|
- ssh
|
|
|
|
- name: "Deployer : SSH : Restart"
|
|
ansible.builtin.systemd_service:
|
|
name: ssh
|
|
state: restarted
|
|
enabled: true
|
|
when:
|
|
- (deployerTaskS1 is defined and deployerTaskS1.changed) or deployerTaskS1 is undefined
|
|
|
|
- name: "Deployer - Yggdrasil - Configure - Create Folder"
|
|
ansible.builtin.file:
|
|
path: "/root/data/yggdrasil/"
|
|
state: directory
|
|
tags:
|
|
- yggdrasil
|
|
|
|
- name: "Deployer - Yggdrasil - Configure - Create Subfolders"
|
|
ansible.builtin.file:
|
|
dest: '/root/data/yggdrasil/{{ item.path }}'
|
|
state: directory
|
|
with_filetree: './files/yggdrasil/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'directory'
|
|
tags:
|
|
- yggdrasil
|
|
|
|
- name: "Deployer - Yggdrasil - Configure - Generating & Transferring Files"
|
|
ansible.builtin.template:
|
|
src: '{{ item.src }}'
|
|
dest: '/root/data/yggdrasil/{{ item.path }}'
|
|
register: deployerTaskY1
|
|
with_filetree: './files/yggdrasil/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'file'
|
|
tags:
|
|
- yggdrasil
|
|
|
|
- name: "Deployer - Yggdrasil - Pull Image"
|
|
containers.podman.podman_image:
|
|
name: docker.io/library/golang
|
|
tag: alpine
|
|
register: deployerTaskY2
|
|
|
|
- name: "Deployer - Yggdrasil - Clone Repository"
|
|
ansible.builtin.git:
|
|
repo: "https://github.com/yggdrasil-network/yggdrasil-go.git"
|
|
dest: ".cache/git/yggdrasil"
|
|
register: deployerTaskY3
|
|
|
|
- name: "Deployer - Yggdrasil - Build Image"
|
|
containers.podman.podman_image:
|
|
name: pvjjk-1vos-niinisalo/yggdrasil
|
|
tag: latest
|
|
path: "/root/data/yggdrasil"
|
|
build:
|
|
format: docker
|
|
force: true
|
|
register: deployerTaskY4
|
|
|
|
- name: "Deployer - Yggdrasil - Run Container"
|
|
containers.podman.podman_container:
|
|
name: yggdrasil
|
|
image: pvjjk-1vos-niinisalo/yggdrasil:latest
|
|
state: started
|
|
recreate: on
|
|
network: host
|
|
capabilities:
|
|
- net_admin
|
|
device:
|
|
- "/dev/net/tun"
|
|
volumes:
|
|
- "/root/data/yggdrasil/config.conf:/etc/yggdrasil-network/config.conf"
|
|
restart_policy: always
|
|
when:
|
|
- (deployerTaskY1 is defined and deployerTaskY1.changed) or deployerTaskY1 is undefined or (deployerTaskY2 is defined and deployerTaskY2.changed) or deployerTaskY2 is undefined or (deployerTaskY3 is defined and deployerTaskY3.changed) or deployerTaskY3 is undefined or (deployerTaskY4 is defined and deployerTaskY4.changed) or deployerTaskY4 is undefined
|
|
tags:
|
|
- yggdrasil
|
|
|
|
- name: "Deployer - MariaDB - Create Folder"
|
|
ansible.builtin.file:
|
|
path: /root/data/mariadb
|
|
state: directory
|
|
tags:
|
|
- mariadb
|
|
- database
|
|
|
|
- name: "Deployer - MariaDB - Pull Image"
|
|
containers.podman.podman_image:
|
|
name: docker.io/library/mariadb
|
|
tag: latest
|
|
register: deployerTaskM1
|
|
|
|
- name: "Deployer - MariaDB - Run Container"
|
|
containers.podman.podman_container:
|
|
name: mariadb
|
|
image: docker.io/library/mariadb:latest
|
|
state: started
|
|
restart: on
|
|
network: host
|
|
volumes:
|
|
- "/root/data/mariadb:/var/lib/mysql"
|
|
restart_policy: always
|
|
env:
|
|
MYSQL_ROOT_PASSWORD: "{{ config.mariadb.users.root.password }}"
|
|
register: deployerTaskM2
|
|
when:
|
|
- (deployerTaskM1 is defined and deployerTaskM1.changed) or deployerTaskM1 is undefined
|
|
tags:
|
|
- mariadb
|
|
- database
|
|
|
|
- name: "Deployer - MariaDB - Wait"
|
|
ansible.builtin.wait_for:
|
|
host: "127.0.0.1"
|
|
port: "3306"
|
|
delay: 10
|
|
when:
|
|
- (deployerTaskM2 is defined and deployerTaskM2.changed) or deployerTaskM2 is undefined
|
|
tags:
|
|
- mariadb
|
|
- database
|
|
|
|
- name: "Deployer - MariaDB - Upgrade"
|
|
containers.podman.podman_container_exec:
|
|
name: "mariadb"
|
|
command: "mariadb-upgrade --host=127.0.0.1 --user=root --password={{ config.mariadb.users.root.password }}"
|
|
register: task
|
|
ignore_errors: yes
|
|
changed_when: task.stdout.find("This installation of MariaDB is already upgraded") == -1
|
|
when:
|
|
- (deployerTaskM2 is defined and deployerTaskM2.changed) or deployerTaskM2 is undefined
|
|
tags:
|
|
- mariadb
|
|
- database
|
|
|
|
- name: "Deployer - MariaDB - Create Users"
|
|
community.mysql.mysql_user:
|
|
login_host: "127.0.0.1"
|
|
login_user: root
|
|
login_password: "{{ config.mariadb.users.root.password }}"
|
|
name: "{{ config.mariadb.users[user].username }}"
|
|
host: "%"
|
|
password: "{{ config.mariadb.users[user].password }}"
|
|
priv: "{{ config.mariadb.users[user].database }}.*:ALL"
|
|
loop: "{{ config.mariadb.users.keys() }}"
|
|
loop_control:
|
|
label: "{{ user }}"
|
|
loop_var: "user"
|
|
when:
|
|
- (deployerTaskM2 is defined and deployerTaskM2.changed) or deployerTaskM2 is undefined
|
|
- config.mariadb.users is defined
|
|
- config.mariadb.users[user] is defined
|
|
- config.mariadb.users[user].username is defined
|
|
- config.mariadb.users[user].password is defined
|
|
- config.mariadb.users[user].database is defined
|
|
tags:
|
|
- mariadb
|
|
- database
|
|
|
|
- name: "Deployer - MariaDB - Create Database"
|
|
community.mysql.mysql_db:
|
|
login_host: "127.0.0.1"
|
|
login_user: "{{ config.mariadb.users[user].username }}"
|
|
login_password: "{{ config.mariadb.users[user].password }}"
|
|
name: "{{ config.mariadb.users[user].database }}"
|
|
loop: "{{ config.mariadb.users.keys() }}"
|
|
loop_control:
|
|
label: "{{ user }}"
|
|
loop_var: "user"
|
|
when:
|
|
- (deployerTaskM2 is defined and deployerTaskM2.changed) or deployerTaskM2 is undefined
|
|
- config.mariadb.users is defined
|
|
- config.mariadb.users[user] is defined
|
|
- config.mariadb.users[user].username is defined
|
|
- config.mariadb.users[user].password is defined
|
|
- config.mariadb.users[user].database is defined
|
|
tags:
|
|
- mariadb
|
|
- database
|
|
|
|
- name: "Deployer - Kea - Install"
|
|
ansible.builtin.apt:
|
|
name:
|
|
- kea
|
|
state: latest
|
|
|
|
- name: "Deployer - Kea - Configure - DHCP4"
|
|
ansible.builtin.template:
|
|
src: './files/kea/kea-dhcp4.conf'
|
|
dest: '/etc/kea/kea-dhcp4.conf'
|
|
register: deployerTaskK1
|
|
tags:
|
|
- kea
|
|
- dhcp
|
|
|
|
- name: "Deployer - Kea - Configure - Database : Init"
|
|
ansible.builtin.command:
|
|
cmd: "/usr/sbin/kea-admin db-init mysql -h 127.0.0.1 -n {{ config.mariadb.users['kea'].database }} -u {{ config.mariadb.users['kea'].username }} -p {{ config.mariadb.users['kea'].password }}"
|
|
register: deployerTaskK2
|
|
changed_when:
|
|
- deployerTaskK2.stdout.find('Initializing database') != -1
|
|
failed_when:
|
|
- deployerTaskK2.stdout.find('ERROR') != -1
|
|
- deployerTaskK2.stdout.find('Expected empty database kea.') == -1
|
|
tags:
|
|
- kea
|
|
- dhcp
|
|
|
|
- name: "Deployer - Kea - Configure - Database : Upgrade"
|
|
ansible.builtin.command:
|
|
cmd: "/usr/sbin/kea-admin db-upgrade mysql -h 127.0.0.1 -n {{ config.mariadb.users['kea'].database }} -u {{ config.mariadb.users['kea'].username }} -p {{ config.mariadb.users['kea'].password }}"
|
|
tags:
|
|
- kea
|
|
- dhcp
|
|
|
|
- name: "Deployer : Kea : Restart"
|
|
ansible.builtin.systemd_service:
|
|
name: kea-dhcp4-server
|
|
state: restarted
|
|
when:
|
|
- (deployerTaskK1 is defined and deployerTaskK1.changed) or deployerTaskK1 is undefined or (deployerTaskK2 is defined and deployerTaskK2.changed) or deployerTaskK2 is undefined
|
|
tags:
|
|
- kea
|
|
- dhcp
|
|
|
|
- name: "Deployer : Kea : Start"
|
|
ansible.builtin.systemd_service:
|
|
name: kea-dhcp4-server
|
|
state: started
|
|
tags:
|
|
- kea
|
|
- dhcp
|
|
|
|
|
|
- name: "Deployer - dnsdist - Configure - Create Folder"
|
|
ansible.builtin.file:
|
|
path: "/root/data/dnsdist/"
|
|
state: directory
|
|
tags:
|
|
- dnsdist
|
|
- dns
|
|
|
|
- name: "Deployer - dnsdist - Configure - Create Subfolders"
|
|
ansible.builtin.file:
|
|
dest: '/root/data/dnsdist/{{ item.path }}'
|
|
state: directory
|
|
with_filetree: './files/dnsdist/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'directory'
|
|
tags:
|
|
- dnsdist
|
|
- dns
|
|
|
|
- name: "Deployer - dnsdist - Configure - Generating & Transferring Files"
|
|
ansible.builtin.template:
|
|
src: '{{ item.src }}'
|
|
dest: '/root/data/dnsdist/{{ item.path }}'
|
|
register: deployerTaskD1
|
|
with_filetree: './files/dnsdist/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'file'
|
|
tags:
|
|
- dnsdist
|
|
- dns
|
|
|
|
- name: "Deployer - dnsdist - Pull Image"
|
|
containers.podman.podman_image:
|
|
name: docker.io/powerdns/dnsdist-20
|
|
tag: latest
|
|
register: deployerTaskD2
|
|
|
|
- name: "Deployer - dnsdist - Run Container"
|
|
containers.podman.podman_container:
|
|
name: dnsdist
|
|
image: docker.io/powerdns/dnsdist-20:latest
|
|
state: started
|
|
recreate: on
|
|
network: host
|
|
restart_policy: always
|
|
volumes:
|
|
- "/root/data/dnsdist/config.conf:/etc/dnsdist/dnsdist.conf:ro"
|
|
tty: yes
|
|
interactive: yes
|
|
capabilities:
|
|
- NET_BIND_SERVICE
|
|
when:
|
|
- (deployerTaskD1 is defined and deployerTaskD1.changed) or deployerTaskD1 is undefined or (deployerTaskD2 is defined and deployerTaskD2.changed) or deployerTaskD2 is undefined
|
|
tags:
|
|
- dnsdist
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Authorative - Configure - Create Folder"
|
|
ansible.builtin.file:
|
|
path: "/root/data/powerdns-authorative/"
|
|
state: directory
|
|
tags:
|
|
- powerdns-authorative
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Authorative - Configure - Create Subfolders"
|
|
ansible.builtin.file:
|
|
dest: '/root/data/powerdns-authorative/{{ item.path }}'
|
|
state: directory
|
|
with_filetree: './files/powerdns-authorative/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'directory'
|
|
tags:
|
|
- powerdns-authorative
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Authorative - Configure - Generating & Transferring Files"
|
|
ansible.builtin.template:
|
|
src: '{{ item.src }}'
|
|
dest: '/root/data/powerdns-authorative/{{ item.path }}'
|
|
register: deployerTaskPA1
|
|
with_filetree: './files/powerdns-authorative/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'file'
|
|
tags:
|
|
- powerdns-authorative
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Authorative - Database - Init"
|
|
community.mysql.mysql_db:
|
|
login_host: "127.0.0.1"
|
|
login_user: "{{ config.mariadb.users['powerdns'].username }}"
|
|
login_password: "{{ config.mariadb.users['powerdns'].password }}"
|
|
name: "{{ config.mariadb.users['powerdns'].database }}"
|
|
state: import
|
|
target: './files/powerdns-authorative/schema.mysql.sql'
|
|
register: deployerTaskPA2
|
|
failed_when:
|
|
- "deployerTaskPA2.msg.find('ERROR') != -1"
|
|
- "deployerTaskPA2.msg.find('already exists') == -1"
|
|
when:
|
|
- config.mariadb.users is defined
|
|
- config.mariadb.users['powerdns'] is defined
|
|
- config.mariadb.users['powerdns'].username is defined
|
|
- config.mariadb.users['powerdns'].password is defined
|
|
- config.mariadb.users['powerdns'].database is defined
|
|
tags:
|
|
- powerdns-authorative
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Authorative - Pull Image"
|
|
containers.podman.podman_image:
|
|
name: docker.io/powerdns/pdns-auth-50
|
|
tag: latest
|
|
register: deployerTaskPA3
|
|
|
|
- name: "Deployer - PowerDNS Authorative - Run Container"
|
|
containers.podman.podman_container:
|
|
name: powerdns-authorative
|
|
image: docker.io/powerdns/pdns-auth-50:latest
|
|
state: started
|
|
recreate: on
|
|
network: host
|
|
restart_policy: always
|
|
volumes:
|
|
- "/root/data/powerdns-authorative/config.conf:/etc/powerdns/pdns.conf:ro"
|
|
capabilities:
|
|
- NET_BIND_SERVICE
|
|
when:
|
|
- (deployerTaskPA1 is defined and deployerTaskPA1.changed) or deployerTaskPA1 is undefined or (deployerTaskPA3 is defined and deployerTaskPA3.changed) or deployerTaskPA3 is undefined
|
|
tags:
|
|
- powerdns-authorative
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Authorative - Configure - Create Zone"
|
|
ansible.builtin.uri:
|
|
url: "http://127.0.0.1:8081/api/v1/servers/localhost/zones"
|
|
method: POST
|
|
headers:
|
|
X-API-Key: "{{ config.powerdns.apiKey }}"
|
|
status_code:
|
|
- 201
|
|
- 409
|
|
body_format: json
|
|
body: "{{ zone | to_json }}"
|
|
register: task
|
|
vars:
|
|
zone:
|
|
name: "tjas."
|
|
kind: native
|
|
ttl: 86400
|
|
changed_when:
|
|
- task.status == 201
|
|
failed_when:
|
|
- task.status != 201
|
|
- task.status != 409
|
|
tags:
|
|
- powerdns-authorative
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Authorative - Configure - Create Records"
|
|
ansible.builtin.uri:
|
|
url: "http://127.0.0.1:8081/api/v1/servers/localhost/zones/tjas."
|
|
method: PATCH
|
|
headers:
|
|
X-API-Key: "{{ config.powerdns.apiKey }}"
|
|
status_code:
|
|
- 204
|
|
body_format: json
|
|
body: "{{ records | to_json }}"
|
|
register: task
|
|
vars:
|
|
records:
|
|
rrsets:
|
|
- name: "tjas."
|
|
type: A
|
|
ttl: 3600
|
|
changetype: REPLACE
|
|
records:
|
|
- content: "192.168.2.10"
|
|
disabled: false
|
|
- name: "tjas."
|
|
type: AAAA
|
|
ttl: 3600
|
|
changetype: REPLACE
|
|
records:
|
|
- content: "201:a6d:ce01:bbe7:2189:66fe:bdb0:17ae"
|
|
disabled: false
|
|
- name: "*.tjas."
|
|
type: A
|
|
ttl: 3600
|
|
changetype: REPLACE
|
|
records:
|
|
- content: "192.168.2.10"
|
|
disabled: false
|
|
- name: "*.tjas."
|
|
type: AAAA
|
|
ttl: 3600
|
|
changetype: REPLACE
|
|
records:
|
|
- content: "201:a6d:ce01:bbe7:2189:66fe:bdb0:17ae"
|
|
disabled: false
|
|
changed_when:
|
|
- task.status == 204
|
|
tags:
|
|
- powerdns-authorative
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Recursor - Configure - Create Folder"
|
|
ansible.builtin.file:
|
|
path: "/root/data/powerdns-recursor/"
|
|
state: directory
|
|
tags:
|
|
- powerdns-recursor
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Recursor - Configure - Create Subfolders"
|
|
ansible.builtin.file:
|
|
dest: '/root/data/powerdns-recursor/{{ item.path }}'
|
|
state: directory
|
|
with_filetree: './files/powerdns-recursor/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'directory'
|
|
tags:
|
|
- powerdns-recursor
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Recursor - Configure - Generating & Transferring Files"
|
|
ansible.builtin.template:
|
|
src: '{{ item.src }}'
|
|
dest: '/root/data/powerdns-recursor/{{ item.path }}'
|
|
register: deployerTaskPR1
|
|
with_filetree: './files/powerdns-recursor/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'file'
|
|
tags:
|
|
- powerdns-recursor
|
|
- dns
|
|
|
|
- name: "Deployer - PowerDNS Recursor - Pull Image"
|
|
containers.podman.podman_image:
|
|
name: docker.io/powerdns/pdns-recursor-52
|
|
tag: latest
|
|
register: deployerTaskPR2
|
|
|
|
- name: "Deployer - PowerDNS Recursor - Run Container"
|
|
containers.podman.podman_container:
|
|
name: powerdns-recursor
|
|
image: docker.io/powerdns/pdns-recursor-52:latest
|
|
state: started
|
|
recreate: on
|
|
network: host
|
|
restart_policy: always
|
|
volumes:
|
|
- "/root/data/powerdns-recursor/config.conf:/etc/powerdns/recursor.conf:ro"
|
|
capabilities:
|
|
- NET_BIND_SERVICE
|
|
when:
|
|
- (deployerTaskPR1 is defined and deployerTaskPR1.changed) or deployerTaskPR1 is undefined or (deployerTaskPR2 is defined and deployerTaskPR2.changed) or deployerTaskPR2 is undefined
|
|
tags:
|
|
- powerdns-recursor
|
|
- dns
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Create Folder"
|
|
ansible.builtin.file:
|
|
path: "/root/data/openssl/{{ cert }}"
|
|
state: directory
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Generate Private Key"
|
|
community.crypto.openssl_privatekey:
|
|
path: "/root/data/openssl/{{ cert }}/privkey.pem"
|
|
type: ECC
|
|
curve: secp384r1
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Generate Certificate Signing Request / Root"
|
|
community.crypto.openssl_csr:
|
|
path: "/root/data/openssl/{{ cert }}/csr.pem"
|
|
privatekey_path: "/root/data/openssl/{{ cert }}/privkey.pem"
|
|
commonName: "{{ config.openssl.certificates[cert].commonName }}"
|
|
organizationName: "{{ config.openssl.certificates[cert].organization.name }}"
|
|
organizationalUnitName: "{{ config.openssl.certificates[cert].organization.unit }}"
|
|
countryName: FI
|
|
basicConstraints:
|
|
- 'CA:TRUE'
|
|
basic_constraints_critical: true
|
|
key_usage:
|
|
- keyCertSign
|
|
key_usage_critical: true
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
when:
|
|
- config.openssl.certificates[cert].location.providence is not defined
|
|
- config.openssl.certificates[cert].location.city is not defined
|
|
- config.openssl.certificates[cert].subjectAltName is undefined
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Generate Certificate Signing Request / Intermediate"
|
|
community.crypto.openssl_csr:
|
|
path: "/root/data/openssl/{{ cert }}/csr.pem"
|
|
privatekey_path: "/root/data/openssl/{{ cert }}/privkey.pem"
|
|
commonName: "{{ config.openssl.certificates[cert].commonName }}"
|
|
organizationName: "{{ config.openssl.certificates[cert].organization.name }}"
|
|
organizationalUnitName: "{{ config.openssl.certificates[cert].organization.unit }}"
|
|
stateOrProvinceName: "{{ config.openssl.certificates[cert].location.providence }}"
|
|
localityName: "{{ config.openssl.certificates[cert].location.city }}"
|
|
countryName: FI
|
|
basicConstraints:
|
|
- 'CA:TRUE'
|
|
basic_constraints_critical: true
|
|
key_usage:
|
|
- keyCertSign
|
|
key_usage_critical: true
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
when:
|
|
- config.openssl.certificates[cert].location.providence is defined
|
|
- config.openssl.certificates[cert].location.city is defined
|
|
- config.openssl.certificates[cert].subjectAltName is undefined
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Generate Certificate Signing Request / Service"
|
|
community.crypto.openssl_csr:
|
|
path: "/root/data/openssl/{{ cert }}/csr.pem"
|
|
privatekey_path: "/root/data/openssl/{{ cert }}/privkey.pem"
|
|
commonName: "{{ config.openssl.certificates[cert].commonName }}"
|
|
organizationName: "{{ config.openssl.certificates[cert].organization.name }}"
|
|
organizationalUnitName: "{{ config.openssl.certificates[cert].organization.unit }}"
|
|
stateOrProvinceName: "{{ config.openssl.certificates[cert].location.providence | default(None) }}"
|
|
localityName: "{{ config.openssl.certificates[cert].location.city | default(None) }}"
|
|
countryName: FI
|
|
subjectAltName: "{{ config.openssl.certificates[cert].subjectAltName }}"
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
when:
|
|
- config.openssl.certificates[cert].subjectAltName is defined
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Generate Certificate / Root"
|
|
community.crypto.x509_certificate:
|
|
path: "/root/data/openssl/{{ cert }}/cert.pem"
|
|
privatekey_path: "/root/data/openssl/{{ cert }}/privkey.pem"
|
|
csr_path: "/root/data/openssl/{{ cert }}/csr.pem"
|
|
provider: selfsigned
|
|
selfsigned_not_after: "+7300d"
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
when:
|
|
- config.openssl.certificates[cert].issuer is undefined
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Copy Certificate / Root"
|
|
ansible.builtin.copy:
|
|
src: "/root/data/openssl/root/cert.pem"
|
|
dest: "/usr/local/share/ca-certificates/root.crt"
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - CA Certificates Update"
|
|
ansible.builtin.command:
|
|
cmd: update-ca-certificates
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Generate Certificate / Intermediate"
|
|
community.crypto.x509_certificate:
|
|
path: "/root/data/openssl/{{ cert }}/cert.pem"
|
|
privatekey_path: "/root/data/openssl/{{ cert }}/privkey.pem"
|
|
csr_path: "/root/data/openssl/{{ cert }}/csr.pem"
|
|
provider: "ownca"
|
|
ownca_path: "/root/data/openssl/{{ config.openssl.certificates[cert].issuer }}/cert.pem"
|
|
ownca_privatekey_path: "/root/data/openssl/{{ config.openssl.certificates[cert].issuer }}/privkey.pem"
|
|
ownca_not_after: "+365d"
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
when:
|
|
- config.openssl.certificates[cert].subjectAltName is undefined
|
|
- config.openssl.certificates[cert].issuer is defined
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Generate Certificate / Service"
|
|
community.crypto.x509_certificate:
|
|
path: "/root/data/openssl/{{ cert }}/cert.pem"
|
|
privatekey_path: "/root/data/openssl/{{ cert }}/privkey.pem"
|
|
csr_path: "/root/data/openssl/{{ cert }}/csr.pem"
|
|
provider: "ownca"
|
|
ownca_path: "/root/data/openssl/{{ config.openssl.certificates[cert].issuer }}/cert.pem"
|
|
ownca_privatekey_path: "/root/data/openssl/{{ config.openssl.certificates[cert].issuer }}/privkey.pem"
|
|
ownca_not_after: "+30d"
|
|
register: deployerTaskO1
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
when:
|
|
- config.openssl.certificates[cert].subjectAltName is defined
|
|
- config.openssl.certificates[cert].issuer is defined
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Generate Fullchain"
|
|
ansible.builtin.copy:
|
|
dest: "/root/data/openssl/{{ cert }}/fullchain.pem"
|
|
content: "{{ lookup('ansible.builtin.file', '/root/data/openssl/' + cert + '/cert.pem') }}\n{{ lookup('ansible.builtin.file', '/root/data/openssl/' + config.openssl.certificates[cert].issuer + '/cert.pem') }}\n{{ lookup('ansible.builtin.file', '/root/data/openssl/root/cert.pem') }}"
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
when:
|
|
- config.openssl.certificates[cert].subjectAltName is defined
|
|
- config.openssl.certificates[cert].issuer is defined
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - OpenSSL - Configure - Generate Chain"
|
|
ansible.builtin.copy:
|
|
dest: "/root/data/openssl/{{ cert }}/chain.pem"
|
|
content: "{{ lookup('ansible.builtin.file', '/root/data/openssl/' + config.openssl.certificates[cert].issuer + '/cert.pem') }}\n{{ lookup('ansible.builtin.file', '/root/data/openssl/root/cert.pem') }}"
|
|
loop: "{{ config.openssl.certificates.keys() | list }}"
|
|
loop_control:
|
|
label: "{{ cert }}"
|
|
loop_var: "cert"
|
|
when:
|
|
- config.openssl.certificates[cert].subjectAltName is defined
|
|
- config.openssl.certificates[cert].issuer is defined
|
|
tags:
|
|
- openssl
|
|
- www
|
|
|
|
- name: "Deployer - Nginx - Configure - Create Folder"
|
|
ansible.builtin.file:
|
|
path: "/root/data/nginx/"
|
|
state: directory
|
|
tags:
|
|
- nginx
|
|
- www
|
|
|
|
- name: "Deployer - Nginx - Configure - Create Subfolders"
|
|
ansible.builtin.file:
|
|
dest: '/root/data/nginx/{{ item.path }}'
|
|
state: directory
|
|
with_filetree: './files/nginx/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'directory'
|
|
tags:
|
|
- nginx
|
|
- www
|
|
|
|
- name: "Deployer - Nginx - Configure - Generating & Transferring Files"
|
|
ansible.builtin.template:
|
|
src: '{{ item.src }}'
|
|
dest: '/root/data/nginx/{{ item.path }}'
|
|
register: deployerTaskN1
|
|
with_filetree: './files/nginx/'
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
when:
|
|
- item.state == 'file'
|
|
tags:
|
|
- nginx
|
|
- www
|
|
|
|
- name: "Deployer - Nginx - Pull Image"
|
|
containers.podman.podman_image:
|
|
name: docker.io/library/nginx
|
|
tag: latest
|
|
register: deployerTaskN2
|
|
|
|
- name: "Deployer - Nginx - Run Container"
|
|
containers.podman.podman_container:
|
|
name: nginx
|
|
image: docker.io/library/nginx:latest
|
|
state: started
|
|
recreate: on
|
|
network: host
|
|
volumes:
|
|
- "/root/data/nginx/index.html:/usr/share/nginx/html/index.html:ro"
|
|
- "/root/data/nginx/config.conf:/etc/nginx/nginx.conf:ro"
|
|
- "/root/data/nginx/conf/:/etc/nginx/conf.d/:ro"
|
|
- "/root/data/openssl/{{ hostname }}/:/etc/nginx/certs/:ro"
|
|
restart_policy: always
|
|
when:
|
|
- (deployerTaskN1 is defined and deployerTaskN1.changed) or deployerTaskN1 is undefined or (deployerTaskN2 is defined and deployerTaskN2.changed) or deployerTaskN2 is undefined or (deployerTaskO1 is defined and deployerTaskO1.changed) or deployerTaskO1 is undefined
|
|
tags:
|
|
- nginx
|
|
- www
|
|
|
|
- name: "Deployer - Uptime Kuma - Pull Image"
|
|
containers.podman.podman_image:
|
|
name: docker.io/louislam/uptime-kuma:latest
|
|
tag: latest
|
|
register: deployerTaskU1
|
|
|
|
- name: "Deployer - Uptime Kuma - Run Container"
|
|
containers.podman.podman_container:
|
|
name: uptime-kuma
|
|
image: docker.io/louislam/uptime-kuma:latest
|
|
state: started
|
|
recreate: on
|
|
network: host
|
|
volumes:
|
|
- "/root/data/uptime-kuma/:/app/data"
|
|
restart_policy: always
|
|
env:
|
|
HOST: 127.0.0.1
|
|
PORT: 3001
|
|
when:
|
|
- (deployerTaskU1 is defined and deployerTaskU1.changed) or deployerTaskU1 is undefined
|
|
tags:
|
|
- uptime-kuma
|
|
- status
|
|
|
|
- name: "Deployer - Keycloak - Pull Image"
|
|
containers.podman.podman_image:
|
|
name: quay.io/keycloak/keycloak:latest
|
|
tag: latest
|
|
register: deployerTaskC1
|
|
|
|
- name: "Deployer - Keacloak - Run Container"
|
|
containers.podman.podman_container:
|
|
name: keacloak
|
|
image: quay.io/keycloak/keycloak:latest
|
|
state: started
|
|
recreate: on
|
|
network: host
|
|
volumes:
|
|
- "/root/data/keycloak/themes:/opt/keycloak/themes"
|
|
restart_policy: always
|
|
env:
|
|
BIND: "127.0.0.1"
|
|
KEYCLOAK_ADMIN: "{{ config.keycloak.admin.username }}"
|
|
KEYCLOAK_ADMIN_PASSWORD: "{{ config.keycloak.admin.password }}"
|
|
PROXY_ADDRESS_FORWARDING: "true"
|
|
KC_DB_URL: "jdbc:mariadb://127.0.0.1:3306/{{ config.mariadb.users['keycloak'].database }}?user={{ config.mariadb.users['keycloak'].username }}&password={{ config.mariadb.users['keycloak'].password }}"
|
|
KC_FEATURES: "preview"
|
|
JAVA_OPTS_APPEND: "-Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=true"
|
|
command: "start --db mariadb --hostname-strict false --proxy-headers xforwarded --http-enabled true --spi-theme-welcome-theme=pvjjk-tjas --log-level=ERROR"
|
|
when:
|
|
- (deployerTaskC1 is defined and deployerTaskC1.changed) or deployerTaskC1 is undefined
|
|
tags:
|
|
- keycloak
|
|
- sso
|
|
|
|
- name: "Deployer - Nextcloud - Files - Create Folder"
|
|
ansible.builtin.file:
|
|
path: "/root/data/nextcloud/{{ folder }}"
|
|
state: directory
|
|
loop: "{{ folders }}"
|
|
loop_control:
|
|
label: "{{ folder }}"
|
|
loop_var: "folder"
|
|
vars:
|
|
folders:
|
|
- html
|
|
- config
|
|
- apps
|
|
- data
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Pull Image"
|
|
containers.podman.podman_image:
|
|
name: docker.io/library/nextcloud:production
|
|
tag: latest
|
|
register: deployerTaskE1
|
|
|
|
- name: "Deployer - Nextcloud - Run Container"
|
|
containers.podman.podman_container:
|
|
name: nextcloud
|
|
image: "docker.io/library/nextcloud:production"
|
|
state: started
|
|
restart: yes
|
|
network_mode: host
|
|
volumes:
|
|
- "/root/data/nextcloud/html:/var/www/html"
|
|
- "/root/data/nextcloud/config:/var/www/html/config"
|
|
- "/root/data/nextcloud/apps:/var/www/html/custom_apps"
|
|
- "/root/data/nextcloud/data:/var/www/html/data"
|
|
restart_policy: always
|
|
env:
|
|
MYSQL_HOST: "127.0.0.1"
|
|
MYSQL_DATABASE: "{{ config.mariadb.users['nextcloud'].database }}"
|
|
MYSQL_USER: "{{ config.mariadb.users['nextcloud'].username }}"
|
|
MYSQL_PASSWORD: "{{ config.mariadb.users['nextcloud'].password }}"
|
|
NEXTCLOUD_ADMIN_USER: "{{ config.nextcloud.users.admin.username }}"
|
|
NEXTCLOUD_ADMIN_PASSWORD: "{{ config.nextcloud.users.admin.password }}"
|
|
NEXTCLOUD_TRUSTED_DOMAINS: "cloud.tjas"
|
|
OVERWRITEPROTOCOL: "https"
|
|
when:
|
|
- (deployerTaskE1 is defined and deployerTaskE1.changed) or deployerTaskE1 is undefined
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Wait"
|
|
ansible.builtin.shell:
|
|
cmd: "podman logs nextcloud"
|
|
register: task
|
|
changed_when:
|
|
- task.stdout.find('Nextcloud was successfully installed') != -1
|
|
until:
|
|
- "task.stdout.find('Nextcloud was successfully installed') != -1 or task.stdout.find('Searching for scripts (*.sh) to run, located in the folder: /docker-entrypoint-hooks.d/before-starting') != -1"
|
|
retries: 5
|
|
delay: 150
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Maintenance Mode : Disable"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ maintenance:mode --off"
|
|
register: task
|
|
ignore_errors: yes
|
|
changed_when:
|
|
- task.stdout != 'Maintenance mode already disabled'
|
|
retries: 5
|
|
delay: 150
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Upgrade"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ upgrade"
|
|
register: task
|
|
ignore_errors: yes
|
|
changed_when:
|
|
- task.stdout != 'No upgrade required.'
|
|
retries: 5
|
|
delay: 150
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Database : Add Missing Indices"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ db:add-missing-indices"
|
|
register: task
|
|
ignore_errors: yes
|
|
changed_when:
|
|
- task.stdout.find('table updated successfully') != -1
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Database : Add Missing Columns"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ db:add-missing-columns"
|
|
register: task
|
|
ignore_errors: yes
|
|
changed_when:
|
|
- task.stdout.find('Done') != -1
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Maintenance : Repair"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ maintenance:repair --include-expensive"
|
|
register: task
|
|
ignore_errors: yes
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Maintenance : Mimetypes : Database"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ maintenance:mimetype:update-db"
|
|
register: task
|
|
ignore_errors: yes
|
|
changed_when:
|
|
- task.stdout.find('Added mimetype') != -1
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Maintenance : Mimetypes : Javascript"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ maintenance:mimetype:update-js"
|
|
register: task
|
|
ignore_errors: yes
|
|
changed_when:
|
|
- task.stdout.find('mimetypelist.js is updated') != -1
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - System : Configure"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ config:system:set {{ entry.key }} --type={% if entry.value is defined and (entry.value == 'true' or entry.value == 'false') %}boolean{% else %}string{% endif %} --value={{ entry.value }}"
|
|
vars:
|
|
entries:
|
|
auth.webauthn.enabled: "false"
|
|
loop: "{{ entries | ansible.builtin.dict2items }}"
|
|
loop_control:
|
|
label: "{{ entry.key }}"
|
|
loop_var: "entry"
|
|
register: task
|
|
changed_when:
|
|
- task.stdout.find('set to string') != -1 or task.stdout.find('set to boolean') != -1
|
|
ignore_errors: yes
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Applications : Disable"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ app:disable {{ application.identifier }}"
|
|
vars:
|
|
applications:
|
|
- name: "Circles"
|
|
identifier: "circles"
|
|
- name: "Contacts Interaction"
|
|
identifier: "contactsinteraction"
|
|
- name: "Federation"
|
|
identifier: "federation"
|
|
- name: "First run wizard"
|
|
identifier: "firstrunwizard"
|
|
- name: "Nextcloud announcements"
|
|
identifier: "nextcloud_announcements"
|
|
- name: "Recommendations"
|
|
identifier: "recommendations"
|
|
- name: "Support"
|
|
identifier: "support"
|
|
- name: "Usage survey"
|
|
identifier: "survey_client"
|
|
- name: "User status"
|
|
identifier: "user_status"
|
|
- name: "Weather status"
|
|
identifier: "weather_status"
|
|
loop: "{{ applications }}"
|
|
loop_control:
|
|
label: "{{ application.name }}"
|
|
loop_var: "application"
|
|
register: task
|
|
changed_when:
|
|
- task.stdout.find('No such app enabled') == -1
|
|
ignore_errors: yes
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Applications : Enable"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ app:enable {{ application.identifier }}"
|
|
vars:
|
|
applications:
|
|
- name: "Calendar"
|
|
identifier: "calendar"
|
|
- name: "Contacts"
|
|
identifier: "contacts"
|
|
- name: "Tasks"
|
|
identifier: "tasks"
|
|
- name: "OpenID Connect Login"
|
|
identifier: "oidc_login"
|
|
loop: "{{ applications }}"
|
|
loop_control:
|
|
label: "{{ application.name }}"
|
|
loop_var: "application"
|
|
register: task
|
|
changed_when:
|
|
- task.stdout.find('already enabled') == -1
|
|
ignore_errors: yes
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Applications : Install"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ app:install {{ application.identifier }}"
|
|
vars:
|
|
applications:
|
|
- name: "Custom CSS"
|
|
identifier: theming_customcss
|
|
- name: "Welcome"
|
|
identifier: welcome
|
|
- name: "Unrounded Corners"
|
|
identifier: unroundedcorners
|
|
- name: "Whiteboard"
|
|
identifier: whiteboard
|
|
loop: "{{ applications }}"
|
|
loop_control:
|
|
label: "{{ application.name }}"
|
|
loop_var: "application"
|
|
register: task
|
|
changed_when:
|
|
- task.stdout.find('already installed') == -1
|
|
failed_when:
|
|
- task.stdout.find('installed') == -1
|
|
- task.stdout.find('already installed') == -1
|
|
ignore_errors: yes
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Applications : Update"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ app:update --all"
|
|
register: task
|
|
changed_when:
|
|
- task.stdout.find('updated') != -1
|
|
ignore_errors: yes
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Applications : Configure - OpenID Connect Login"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ config:system:set {{ entry.key }} --type={% if entry.value is defined and (entry.value == 'true' or entry.value == 'false') %}boolean{% else %}string{% endif %} --value={{ entry.value }}"
|
|
vars:
|
|
entries:
|
|
oidc_login_client_id: "{{ config.nextcloud.integrations.sso.client.id }}"
|
|
oidc_login_client_secret: "{{ config.nextcloud.integrations.sso.client.secret }}"
|
|
oidc_login_provider_url: "https://sso.tjas/realms/master"
|
|
oidc_login_end_session_redirect: "true"
|
|
oidc_login_logout_url: "https://cloud.tjas/apps/oidc_login/oidc"
|
|
oidc_login_auto_redirect: "true"
|
|
oidc_login_redir_fallback: "true"
|
|
"oidc_login_attributes id": "preferred_username"
|
|
"oidc_login_attributes mail": "email"
|
|
oidc_login_scope: "'{{ config.nextcloud.integrations.sso.scope }}'"
|
|
overwriteprotocol: "https"
|
|
allow_user_to_change_display_name: "false"
|
|
lost_password_link: disabled
|
|
oidc_login_button_text: "'PVJJK TJAS'"
|
|
oidc_login_hide_password_form: "true"
|
|
"oidc_login_attributes groups": "groups"
|
|
oidc_login_disable_registration: "false"
|
|
oidc_create_groups: "true"
|
|
oidc_login_webdav_enabled: "true"
|
|
oidc_login_password_authentication: "false"
|
|
loop: "{{ entries | ansible.builtin.dict2items }}"
|
|
loop_control:
|
|
label: "{{ entry.key }}"
|
|
loop_var: "entry"
|
|
register: task
|
|
changed_when:
|
|
- task.stdout.find('set to string') != -1 or task.stdout.find('set to boolean') != -1
|
|
ignore_errors: yes
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|
|
|
|
- name: "Deployer - Nextcloud - Configure - Maintenance Mode : Disable"
|
|
containers.podman.podman_container_exec:
|
|
container: nextcloud
|
|
user: www-data
|
|
command: "./occ maintenance:mode --off"
|
|
register: task
|
|
ignore_errors: yes
|
|
changed_when:
|
|
- task.stdout != 'Maintenance mode already disabled'
|
|
retries: 5
|
|
delay: 150
|
|
tags:
|
|
- nextcloud
|
|
- cloud
|