cytopia / devilbox

A modern Docker LAMP stack and MEAN stack for local development
http://devilbox.org
MIT License
4.4k stars 654 forks source link

[Bug]: Failed to create Certificate Authority. #1025

Open Lucenabo opened 5 months ago

Lucenabo commented 5 months ago

Have you already looked into this bug?

(Optional) Error message

httpd_1 | [FAIL] Failed to create Certificate Authority. httpd_1 | [FAIL] ca-gen -v -c DE -s Berlin -l Berlin -o Devilbox -u Devilbox -n 'Devilbox Root CA' -e 'cytopia@devilbox.org' "/ca/devilbox-ca.key" "/ca/devilbox-ca.crt" httpd_1 | ca-gen: [INFO] Create CA KEY file: /ca/devilbox-ca.key httpd_1 | ca-gen: [ERR] Command: openssl genrsa -out /ca/devilbox-ca.key 2048 httpd_1 | ca-gen: [ERR] Output: genrsa: Can't open "/ca/devilbox-ca.key" for writing, Permission denied devilbox_httpd_1 exited with code 1

What went wrong?

When I start the docker I get this error and can't see the localhost page

Expected behaviour

I should go to localhost and see the panel.

How can we reproduce the bug?

When I make "sudo docker-compose up" it happens. Screenshot from 2024-06-21 11-57-48

Host Operating System

Linux

Host Platform (amd64, arm64, other)

amd64

(Linux only) Is SELinux enabled?

Yes, SELinux is enabled

Docker version

Docker version 24.0.5, build %{shortcommit_cli}

Docker Compose version

docker-compose version 1.29.2, build unknown

Devilbox version

986f0463

Have you removed stopped containers before starting?

Yes

Have you pulled latest Docker images before starting?

Yes

Devilbox start command

sudo docker-compose up

Config: .env file

###
###  ---------------------------------------------------
###  D E V I L B O X   R U N - T I M E   S E T T I N G S
###  ---------------------------------------------------
###
###  All the following settings are applied during
###  $ docker-compose up
###
###  No need to rebuild any docker images!
###
###  IMPORTANT:
###  ----------
###  When changing any values ensure to stop, rm and restart:
###  $ docker-compose stop
###  $ docker-compose rm -f
###  $ docker-compose up
###
###  NOTE:
###  -----
###  For you own custom variables, scroll to the bottom
###

# The following line will disable any shellcheck warnings throughout this file
# shellcheck disable=SC2034,SC2125

###
### Show all executed commands in each
### docker image during docker-compose up?
###
### 0: Errors
### 1: Errors, Warnings
### 2: Errors, Warnings, Infos  (Recommended)
### 3: Errors, Warnings, Infos, Debug
### 4: Errors, Warnings, Infos, Debug, Trace
DEBUG_ENTRYPOINT=2

###
### Log to file or Docker logs.
###
### Logging to file means log files are available under log/
### on your host operating system.
### Logging to Docker logs means log files are streamed to
### stdout and stderr.
###
### 1: Log to Docker logs
### 0: Log to file
###
DOCKER_LOGS=0

###
### Relative or absolute path to the devilbox repository.
### (Used as a prefix for all mount paths)
### There is no need to change this.
###
### The only exception is for OSX users wanting to use NFS
### mounts instead of Filesystem mounts due to degraded performance
### on OSX.
###
### Note: When changing this variable you must re-create the container.
###       Simply remove it, it will be auto-created during the next start:
###
### $ docker-compose rm -f
###
DEVILBOX_PATH=.

###
### At what IP address should the docker services listen
### on the Host computer?
###
### The specified default should be fine for Linux and OSX (127.0.0.1:).
### If you are on windows, you will probably have to change
### it to the IP address of the docker machine.
###
### a.) Leave blank, to listen on all interfaces (no trailing colon ':')
###    LOCAL_LISTEN_ADDR=
### b.) If an IP is specified, note the trailing colon ':'
###    LOCAL_LISTEN_ADDR=127.0.0.1:
###
LOCAL_LISTEN_ADDR=

###
### This is the domain suffix your projects will be made available
### with mass-virtual-hosting.
### It is also required for the internal DNS server to be setup.
###
### Note: In addition to period or dot character ('.'), only ALPHA ([a-zA-Z]+) characters are supported.
###       Mac users should not use the .local TLD, as this will not resolve properly due Apple's
###       use of Multicast DNS.
###
### Note: If you use 'dvl.to' then there is no need for additional DNS as this always points
###       to 127.0.0.1 by default.
###
### Example:
###   TLD_SUFFIX=loc
### Makes your project available under <project>.loc
###
### Example:
###   TLD_SUFFIX=local
### Makes your project available under <project>.local
###
TLD_SUFFIX=dvl.to

###
### Optional DNS configuration
### Allows you to add extra DNS records (above the wildcard entry)
### Useful if your host computer run other Docker services that you want to connect to or reach
### from within the Devilbox network by a custom hostname.
###
### Format:
### -------
### Resolve any custom defined hostname to an IP address (useable inside container and host os)
###     EXTRA_HOSTS=<hostname>=<ip>[,<hostname>=<ip>]
###
### Resolve any custom defined hostname to whatever IP address a CNAME resolves to
### (Useable inside the container and host OS).
### Note: CNAME must be resolvable by Google DNS
###     EXTRA_HOSTS=<hostname>=<CNAME>[,<hostname>=<CNAME>]
###
### Examples:
### ---------
### EXTRA_HOSTS=hostname.loc=1.2.3.4
### EXTRA_HOSTS=host.loc=1.2.3.4,host.example.org=3.4.5.6
EXTRA_HOSTS=

###
### Set your user id and group id
###
### This should be changed to the value of your local
### users uid and gid
###
### Type `id` on the terminal to find out your values
###
NEW_UID=1000
NEW_GID=1000

###
### Timezone for PHP Docker container (system and php.ini)
###
TIMEZONE=UTC

################################################################################
###
### INTRANET SETTINGS
###
################################################################################

###
### TLD_SUFFIX domains are checked if they are set in the
### host computer /etc/hosts or available via attached DNS server.
### Timeout is done on vhosts.php (intranet) via ajax calls.
### In order to keep performance, set this to a low value.
### DNS checks might not succeed in time on slow machines.
### If DNS is valid, but timeout is expired, set this to a higher value.
###
### DNS_CHECK_TIMEOUT value is how many seconds to time out
### Default is to timeout after 1 second (DNS_CHECK_TIMEOUT=1)
###
DNS_CHECK_TIMEOUT=1

###
### Devilbox UI SSL Certificate generation
###
### When using SSL each certificate requires names for which it is responsible:
### Common Name as well as alternative names.
###
### Specify comma separated hostnames below by which you want to access the Devilbox.
### Those hostnames will be included in the SSL certificate for the Devilbox intranet.
### This has nothing to do for SSL certificates for projects, it is just for the intranet
### itself.
###
DEVILBOX_UI_SSL_CN=localhost,*.localhost,devilbox,*.devilbox,httpd

###
### Devilbox UI Password protection enable/disable (1/0)
###
### Set DEVILBOX_UI_PROTECT to 1 in order to password protect the
### intranet.
###
### Example:
###   DEVILBOX_UI_PROTECT=1
###   DEVILBOX_UI_PROTECT=0
###
DEVILBOX_UI_PROTECT=0

###
### Devilbox UI Password
###
### When DEVILBOX_UI_PROTECT=1, use the following password
### to log in. The password can always be changed.
### When changing the password, make sure to restart your
### PHP container.
###
### Example:
###   DEVILBOX_UI_PASSWORD=my-very-secure-password
###   DEVILBOX_UI_PASSWORD=Some pass with spaces
###
### The default username is 'devilbox'
###
DEVILBOX_UI_PASSWORD=password

###
### Enable the Devilbox Intranet?
###
### Example:
###   DEVILBOX_UI_ENABLE=1
###   DEVILBOX_UI_ENABLE=0
###
DEVILBOX_UI_ENABLE=1

###
### Automatically be logged in into phpMyAdmin
###
### Example:
###   DEVILBOX_VENDOR_PHPMYADMIN_AUTOLOGIN=1
###   DEVILBOX_VENDOR_PHPMYADMIN_AUTOLOGIN=0
###
DEVILBOX_VENDOR_PHPMYADMIN_AUTOLOGIN=1

###
### Automatically be logged in into phpPgAdmin
###
### Example:
###   DEVILBOX_VENDOR_PHPPGADMIN_AUTOLOGIN=1
###   DEVILBOX_VENDOR_PHPPGADMIN_AUTOLOGIN=0
###
DEVILBOX_VENDOR_PHPPGADMIN_AUTOLOGIN=1

###
### HTTPD Supvervisord management
###
DEVILBOX_HTTPD_MGMT_USER=supervisord
DEVILBOX_HTTPD_MGMT_PASS=mypassword

################################################################################
###
### 1. Choose Images (Version)
###
################################################################################

###
### You can choose any combination of httpd, mysql, postgresql or php.
### Each of them are fully compatible between one another.
###

###
### 1.1 Choose PHP Server Image
###
### Note: PHP 5.2 is not officially supported. Intranet won't work (due to lack of namespace support).
###       PHP 5.2 only works with Apache 2.4, Nginx stable and Nginx mainline.
###       Use at your own risk.
###
#PHP_SERVER=5.2
#PHP_SERVER=5.3
#PHP_SERVER=5.4
#PHP_SERVER=5.5
#PHP_SERVER=5.6
#PHP_SERVER=7.0
#PHP_SERVER=7.1
#PHP_SERVER=7.2
#PHP_SERVER=7.3
#PHP_SERVER=7.4
#PHP_SERVER=8.0
PHP_SERVER=8.1
#PHP_SERVER=8.2

###
### 1.2 Choose HTTPD Server Image
###
### Choose between 'debian' or 'alpine' flavour and then select the version
###
### Note: apache-2.2 has no arm64 support on 'alpine' flavour
###
HTTPD_FLAVOUR=alpine

#HTTPD_SERVER=apache-2.2
#HTTPD_SERVER=apache-2.4
HTTPD_SERVER=nginx-stable
#HTTPD_SERVER=nginx-mainline

###
### 1.3 Choose MySQL Server Image
###
#MYSQL_SERVER=mysql-5.5
#MYSQL_SERVER=mysql-5.6
#MYSQL_SERVER=mysql-5.7
#MYSQL_SERVER=mysql-8.0
#MYSQL_SERVER=percona-5.5
#MYSQL_SERVER=percona-5.6
#MYSQL_SERVER=percona-5.7
#MYSQL_SERVER=percona-8.0
#MYSQL_SERVER=mariadb-5.5
#MYSQL_SERVER=mariadb-10.0
#MYSQL_SERVER=mariadb-10.1
#MYSQL_SERVER=mariadb-10.2
#MYSQL_SERVER=mariadb-10.3
#MYSQL_SERVER=mariadb-10.4
#MYSQL_SERVER=mariadb-10.5
MYSQL_SERVER=mariadb-10.6
#MYSQL_SERVER=mariadb-10.7
#MYSQL_SERVER=mariadb-10.8
#MYSQL_SERVER=mariadb-10.9
#MYSQL_SERVER=mariadb-10.10

###
### 1.4 Choose PostgreSQL Server Image
###
### https://www.postgresql.org/support/versioning/
###
### IMPORTANT: Alpine based images might cause issues on Docker Toolbox or OSX
###            https://github.com/docker/toolbox/issues/510
###
#
# PostgreSQL without arm64 support
#
#PGSQL_SERVER=9.0
#PGSQL_SERVER=9.1
#PGSQL_SERVER=9.2-alpine
#
# PostgreSQL with arm64 support
#
#PGSQL_SERVER=9.2
#PGSQL_SERVER=9.3
#PGSQL_SERVER=9.3-alpine
#PGSQL_SERVER=9.4
#PGSQL_SERVER=9.4-alpine
#PGSQL_SERVER=9.5
#PGSQL_SERVER=9.5-alpine
#PGSQL_SERVER=9.6
#PGSQL_SERVER=9.6-alpine
#PGSQL_SERVER=10
#PGSQL_SERVER=10-alpine
#PGSQL_SERVER=11
#PGSQL_SERVER=11-alpine
#PGSQL_SERVER=12
#PGSQL_SERVER=12-alpine
#PGSQL_SERVER=13
#PGSQL_SERVER=13-alpine
#PGSQL_SERVER=14
PGSQL_SERVER=14-alpine
#PGSQL_SERVER=15
#PGSQL_SERVER=15-alpine
#PGSQL_SERVER=latest
#PGSQL_SERVER=alpine

###
### 1.5 Choose Redis Server Image
###
### IMPORTANT: Alpine based images might cause issues on Docker Toolbox or OSX
###            https://github.com/docker/toolbox/issues/510
###
#
# Redis without arm64 support
#
#REDIS_SERVER=2.8
#REDIS_SERVER=3.0
#REDIS_SERVER=3.0-alpine
#
# Redis with arm64 support
#
#REDIS_SERVER=3.2
#REDIS_SERVER=3.2-alpine
#REDIS_SERVER=4.0
#REDIS_SERVER=4.0-alpine
#REDIS_SERVER=5.0
#REDIS_SERVER=5.0-alpine
#REDIS_SERVER=6.0
#REDIS_SERVER=6.0-alpine
#REDIS_SERVER=6.2
REDIS_SERVER=6.2-alpine
#REDIS_SERVER=7.0
#REDIS_SERVER=7.0-alpine
#REDIS_SERVER=latest
#REDIS_SERVER=alpine

###
### 1.6 Choose Memcached Server Image
###
### IMPORTANT: Alpine based images might cause issues on Docker Toolbox or OSX
###            https://github.com/docker/toolbox/issues/510
###
#
# Memcached without arm64 support
#
#MEMCD_SERVER=1.4
#MEMCD_SERVER=1.4-alpine
#
# Memcached with arm64 support
#
#MEMCD_SERVER=1.5
#MEMCD_SERVER=1.5-alpine
#MEMCD_SERVER=1.6
MEMCD_SERVER=1.6-alpine
#MEMCD_SERVER=latest
#MEMCD_SERVER=alpine

###
### 1.7 Choose Mongo Server Image
###
### https://www.mongodb.com/evolved
###
#
# MongoDB without arm64 support
#
#MONGO_SERVER=2.8
#MONGO_SERVER=3.0
#MONGO_SERVER=3.2
#
# MongoDB with arm64 support
#
#MONGO_SERVER=3.4
#MONGO_SERVER=3.6
#MONGO_SERVER=4.0
#MONGO_SERVER=4.2
#MONGO_SERVER=4.4
MONGO_SERVER=5.0
#MONGO_SERVER=latest

################################################################################
###
### 2. Host Mounts (Your computer)
###
################################################################################

###
### Global mount options
###
### Note: When adding custom mount options, ensure to start with a
###       leading ',' (comma), as those options are prepended to already
###       existing mount options.
###
### Note: If no mount options are specified, leave this variable empty
###       and do not add a leading ',' (comma).
###
### MOUNT_OPTIONS=,cached
### MOUNT_OPTIONS=
###
### Example: Allow to share mounts accross container with SELINUX enabled
###
### MOUNT_OPTIONS=,z
###
MOUNT_OPTIONS=

###
### Local filesystem path to www projects.
###
### Note: When changing this variable you must re-create the container.
###       Simply remove it, it will be auto-created during the next start:
###
### $ docker-compose rm -f
###
HOST_PATH_HTTPD_DATADIR=./data/www

###
### Local filesystem path to where your backups are stored
###
### Note: When changing this variable you must re-create the container.
###       Simply remove it, it will be auto-created during the next start:
###
### $ docker-compose rm -f
###
HOST_PATH_BACKUPDIR=./backups

###
### The path on your host OS of the ssh directory to be mounted into the
### PHP container into /home/devilbox/.ssh.
###
### IMPORTANT: The path is mounted read-only to ensure you cannot accidentally
##             delete anything inside the php container.
###
HOST_PATH_SSH_DIR=~/.ssh

################################################################################
###
### 3. PHP Docker Settings
###
################################################################################

###
### Enable certain PHP modules which are not enabled by default
###
### Currently the only modules that can be enabled are 'ioncube' and 'blackfire'
### Also ensure to disable xdebug when using any of the above:
### https://xdebug.org/docs/install#compat
###
### PHP_MODULES_ENABLE=ioncube, blackfire
###
PHP_MODULES_ENABLE=

###
### Disable any PHP modules that you don't require
###
### Specify a comma separated list without spaces of modules to disable
###
### PHP_MODULES_DISABLE=xdebug,imagick,swoole
###
PHP_MODULES_DISABLE=oci8,PDO_OCI,pdo_sqlsrv,sqlsrv,rdkafka,swoole,psr,phalcon

###
### Postfix settings for email catch-all
###
### When set to '1' postfix is normally started and made available. However you still need
### to configure it to your needs yourself. For that you can use the autostart scripts
### and define a couple of 'postconf -e name=value' commands.
###
### When set to '2' (email catch-all), no mail will leave the Devilbox. It is automatically
### internally routed the the devilbox mail account and you can see each sent mail
### in the bundled intranet: https://localhost/mail.php
###
### Values:
### 0: Disable postfix (do not start it)
### 1: Enable/Start postfix
### 2: Enable/Start postfix and enable email catch-all
###
PHP_MAIL_CATCH_ALL=2

###
### Configure everything else about PHP in
### * cfg/php-ini-X.X/*.ini
### * cfg/php-fpm-X.X/*.conf

################################################################################
###
### 4. HTTPD Docker Settings
###
################################################################################

###
### Expose HTTPD Port to Host
###
HOST_PORT_HTTPD=80
HOST_PORT_HTTPD_SSL=443

###
### Globally enable/disable HTTP/2 support
###
### This cannot be done on a per vhost level and must be enabled/disabled globally.
###
### Values:
###  * 0: HTTP/2 is disabled
###  * 1: HTTP/2 is enabled
###
HTTPD_HTTP2_ENABLE=1

###
### SSL (HTTP/HTTPS) settings for automated vhost generation
###
### By default each project will have two vhosts (one for HTTP and one for HTTPS).
### You can control the SSL settings for your projects via the below stated values.
###
### This is internally achieved via the '-m' argument of https://github.com/devilbox/vhost-gen
###
### Values:
###   * both:  Serve HTTP and HTTPS for all projects
###   * redir: HTTP always redirects to HTTPS
###   * ssl:   Only serve HTTPS
###   * plain: Only serve HTTP
###
HTTPD_VHOST_SSL_TYPE=both

###
### Document Root Subdirectory
###
### In your project directory, which subfolder should
### serve your files?
###
### When changing this value, restart the devilbox.
###
HTTPD_DOCROOT_DIR=htdocs

###
### Per vHost Config Subdirectory
###
### In your project directory, which subfolder should
### hold apache, nginx templates for a customized vhost?
###
### When changing this value, restart the devilbox.
###
HTTPD_TEMPLATE_DIR=.devilbox

###
### Remote (Upstream) Backend Timeout
###
### This setting specifies the Timeout for a remote Backend server,
### such as PHP-FPM or a Reverse Proxy.
###
### As for PHP, keep in mind that this value should be greater than
### PHP's max_execution_time,otherwise the php script could still
### run and the webserver will simply drop the connection before getting an answer by PHP.
###
HTTPD_BACKEND_TIMEOUT=180

###
### NGINX ONLY
###
### Set worker_processes and worker_connections
###
### https://nginx.org/en/docs/ngx_core_module.html#worker_processes
### https://nginx.org/en/docs/ngx_core_module.html#worker_connections
###
HTTPD_NGINX_WORKER_PROCESSES=auto
HTTPD_NGINX_WORKER_CONNECTIONS=1024

################################################################################
###
### 5. MySQL Docker Settings
###
################################################################################

###
### MySQL root user password
###
### The password is required for the initial creation of the MySQL database
### as well as the Devilbox intranet to display schema and configuration settings.
###
### If you change your MySQL root user password via mysql cli, phpMyAdmin or other tools
### after the database has been created, ensure to adjust the value here accordingly as well.
###
### If you only change this value here after the database has been created,
### the MySQL root user password will not actually be changed and the Devilbox intranet
### won't be able to connect to the MySQL service.
###
MYSQL_ROOT_PASSWORD=

###
### Expose MySQL Port to Host
###
HOST_PORT_MYSQL=3306

################################################################################
###
### 6. PostgreSQL Docker Settings
###
################################################################################

###
### PostgreSQL 'root' user name (usually postgres)
###
PGSQL_ROOT_USER=postgres

###
### PostgreSQL 'root' user password
###
### If you want to set a password, ensure to remove 'trust' from
### PGSQL_HOST_AUTH_METHOD below
###
PGSQL_ROOT_PASSWORD=

###
### In order to not use a password for PostgreSQL, keep this value at 'trust'
###
PGSQL_HOST_AUTH_METHOD=trust

###
### Expose PostgreSQL Port to Host
###
HOST_PORT_PGSQL=5432

################################################################################
###
### 7. Redis Docker Settings
###
################################################################################

###
### Expose Redis Port to Host
###
HOST_PORT_REDIS=6379

###
### Custom startup arguments
###
### Apply custom startup arguments to redis
###
### Example: Password protection
###   Add password protection to the Redis server by specifying it should
###   require a password.
###   Note: Do not add quotes or spaces to the password
###
###   REDIS_ARGS=--requirepass my-redis-root-password
###
### Example: Verbosity
###
###   REDIS_ARGS=--loglevel verbose
###
REDIS_ARGS=
#REDIS_ARGS=--loglevel verbose --requirepass my-redis-root-password

################################################################################
###
### 8. Memcached Docker Settings
###
################################################################################

###
### Expose Memcached Port to Host
###
HOST_PORT_MEMCD=11211

################################################################################
###
### 9. MongoDB Docker Settings
###
################################################################################

###
### Expose MongoDB Port to Host
###
HOST_PORT_MONGO=27017

################################################################################
###
### 10. Bind Docker Settings
###
################################################################################

###
### Expose Bind Port to Host
###
HOST_PORT_BIND=1053

###
### Add comma separated DNS server from which you want to receive DNS
### You can also add DNS servers from your LAN (if any are available)
###
BIND_DNS_RESOLVER=8.8.8.8,8.8.4.4

###
### Validate DNSSEC
###
### Values:
###  no:    DNSSEC validation is disabled
###  yes:   DNSSEC validation is enabled, but a trust anchor must be manually configured.
###  auto:  DNSSEC validation is enabled, and a default trust anchor for root zone is used.
###
BIND_DNSSEC_VALIDATE=no

###
### Bind timing options (time in seconds)
###
### Leave empty for defaults.
### Only change when you know what you are doing.
###
BIND_TTL_TIME=
BIND_REFRESH_TIME=
BIND_RETRY_TIME=
BIND_EXPIRY_TIME=
BIND_MAX_CACHE_TIME=

###
### Show DNS Queries in Docker logs output?
###
### 1: Yes
### 0: No
BIND_LOG_DNS_QUERIES=0

################################################################################
###
### 11. Custom variables
###
################################################################################

###
### Any variable defined in this file will be available
### as environment variables to your PHP/HHV Docker container.
###
### This might be useful to set application environment and retrieve
### them via: <?php getenv('MY_APPLICATION_ENV'); ?>
###

###
### Example:
### <?php echo getenv('Foo'); ?> would produce: 'some value'
###
#Foo=some value

Config: docker-compose.override.yml

# IMPORTANT: The version must match the version of docker-compose.yml
---
version: '2.3'

# The following override shows an example for the cockroachdb
services:
  # Your custom Docker image here:
  cockroach:
    image: cockroachdb/cockroach:latest
    command: start --insecure
    # Ensure the chosen ports are not occupied on the host system
    ports:
      - "${LOCAL_LISTEN_ADDR}26257:26257"
      - "${LOCAL_LISTEN_ADDR}8080:8080"
    networks:
      app_net:
        # Ensure to pick an IP address from docker-compose.yml network
        # that is not yet taken by other sevices
        ipv4_address: 172.16.238.200
    # (Optional) For ease of use always automatically start these:
    depends_on:
      - bind
      - php
      - httpd

Config: ./check-config.sh

#!/usr/bin/env bash

set -e
set -u
set -o pipefail

#--------------------------------------------------------------------------------------------------
# GLOBALS
#--------------------------------------------------------------------------------------------------
RET_CODE=0
MY_UID="$( id -u )"
MY_GID="$( id -g )"
DEBUG=0

#--------------------------------------------------------------------------------------------------
# Functions
#--------------------------------------------------------------------------------------------------

###
### Logger functions
###
log_err() {
    >&2 printf "\\e[1;31m[ERR]   %s\\e[0m\\n" "${1}"
}

log_note() {
    >&2 printf "\\e[1;33m[NOTE]  %s\\e[0m\\n" "${1}"
}

log_info() {
    printf "\\e[;34m[INFO]  %s\\e[0m\\n" "${1}"
}

log_ok() {
    printf "\\e[;32m[SUCC]  %s\\e[0m\\n" "${1}"
}
log_debug() {
    if [ "${DEBUG}" -eq "1" ]; then
        printf "[DEBUG] %s\\n" "${1}"
    fi
}

###
### Output functions
###
print_head_1() {
    printf "\\n# "
    printf "%0.s=" {1..78}
    printf "\\n"

    printf "# %s\\n" "${1}"

    printf "# "
    printf "%0.s=" {1..78}
    printf "\\n"
}

###
### File functions
###
file_get_uid() {
    if [ "$(uname)" = "Linux" ]; then
        stat --format '%u' "${1}"
    else
        stat -f '%u' "${1}"
    fi
}

file_get_gid() {
    if [ "$(uname)" = "Linux" ]; then
        stat --format '%g' "${1}"
    else
        stat -f '%g' "${1}"
    fi
}

# Returns 4-digit format
file_get_perm() {
    local perm
    local len

    if [ "$(uname)" = "Linux" ]; then
        # If no special permissions are set (no sticky bit...), linux will
        # only output the 3 digit number
        perm="$( stat --format '%a' "${1}" )"
    else
        perm="$( stat -f '%OLp' "${1}" )"
    fi

    # For special cases check the length and add a leading 0
    len="$(echo "${perm}" | awk '{ print length() }')"
    if [ "${len}" = "3" ]; then
        perm="0${perm}"
    fi

    echo "${perm}"
}

# Get path with '~' replace with correct home path
get_path() {
    echo "${1/#\~/${HOME}}"
}

# Returns sub directories by one level
# Also returns symlinks if they point to a directory
get_sub_dirs_level_1() {
    local dir="${1}"
    dir="${dir#./}"   # Remove leading './' if it exists
    dir="${dir%/}"    # Remove trailing '/' if it exists
    # shellcheck disable=SC2016
    find "${dir}" \
        | grep -Ev "^${dir}\$" \
        | grep -Ev "^${dir}/.+/" \
        | xargs -n1 sh -c 'if [ -d "${1}" ]; then echo "${1}"; fi'  -- \
        | sort
}

# Returns sub directories by two level
# Also returns symlinks if they point to a directory
get_sub_dirs_level_2() {
    local dir="${1}"
    dir="${dir#./}"   # Remove leading './' if it exists
    dir="${dir%/}"    # Remove trailing '/' if it exists
    # shellcheck disable=SC2016
    find "${dir}" \
        | grep -Ev "^${dir}\$" \
        | grep -Ev "^${dir}/.+/.+/" \
        | xargs -n1 sh -c 'if [ -d "${1}" ]; then echo "${1}"; fi'  -- \
        | sort
}

# Returns the value of .env var
get_env_value() {
    local val
    val="$( grep -E "^${1}=" .env )"
    echo "${val#*=}"
}

# Validate a DNS record
validate_dns() {
    ping -c1 "${1}" >/dev/null 2>&1
}

#--------------------------------------------------------------------------------------------------
# Check git
#--------------------------------------------------------------------------------------------------
print_head_1 "Checking git"

GIT_STATUS="$( git status -s )"
if [ -z "${GIT_STATUS}" ]; then
    log_ok "git is clean"
else
    log_err "git is unclean"
    echo "${GIT_STATUS}"
    RET_CODE=$(( RET_CODE + 1))
fi

#--------------------------------------------------------------------------------------------------
# Check env file
#--------------------------------------------------------------------------------------------------
print_head_1 "Checking .env file"

if [ -f .env ]; then
    log_ok ".env file exists"
else
    log_err ".env file does not exist"
    RET_CODE=$(( RET_CODE + 1))
    exit 1
fi
if [ -r .env ]; then
    log_ok ".env file is readable"
else
    log_err ".env file is not readable"
    RET_CODE=$(( RET_CODE + 1))
    exit 1
fi

# Ensure all variables exist in .env file
ENV_VAR_MISSING=0
while read -r env_var; do
    if ! grep -E "^${env_var}=" .env >/dev/null; then
        log_err "Variable '${env_var}' missing in .env file"
        RET_CODE=$(( RET_CODE + 1))
        ENV_VAR_MISSING=1
    else
        log_debug "Variable '${env_var}' is present in '.env file"
    fi
done < <(grep -E '^[A-Z].+=' env-example  | awk -F'=' '{print $1}')
if [ "${ENV_VAR_MISSING}" = "0" ]; then
    log_ok "All variables are present in .env file"
fi

# Ensure variables are not duplicated in .env
ENV_VAR_DUPLICATED=0
while read -r env_var; do
    OCCURANCES="$( grep -Ec "^${env_var}=" .env )"
    if [ "${OCCURANCES}" != "1" ]; then
        log_err "Variable '${env_var}' should only be defined once. Occurances: ${OCCURANCES}"
        RET_CODE=$(( RET_CODE + 1))
        ENV_VAR_DUPLICATED=1
    else
        log_debug "Variable '${env_var}' is defined exactly once."
    fi
done < <(grep -E '^[A-Z].+=' env-example  | awk -F'=' '{print $1}')
if [ "${ENV_VAR_DUPLICATED}" = "0" ]; then
    log_ok "No variables is duplicated in .env file"
fi

#--------------------------------------------------------------------------------------------------
# Check env file values
#--------------------------------------------------------------------------------------------------
print_head_1 "Checking .env file values"

WRONG_ENV_FILES_VALUES=0

DEBUG_ENTRYPOINT="$( get_env_value "DEBUG_ENTRYPOINT" )"
if [ "${DEBUG_ENTRYPOINT}" != "0" ] && [ "${DEBUG_ENTRYPOINT}" != "1" ] && [ "${DEBUG_ENTRYPOINT}" != "2" ] && [ "${DEBUG_ENTRYPOINT}" != "3" ] && [ "${DEBUG_ENTRYPOINT}" != "4" ]; then
    log_err "Variable 'DEBUG_ENTRYPOINT' should be 0, 1, 2, 3 or 4. Has: ${DEBUG_ENTRYPOINT}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'DEBUG_ENTRYPOINT' has correct value: ${DEBUG_ENTRYPOINT}"
fi

DOCKER_LOGS="$( get_env_value "DOCKER_LOGS" )"
if [ "${DOCKER_LOGS}" != "0" ] && [ "${DOCKER_LOGS}" != "1" ]; then
    log_err "Variable 'DOCKER_LOGS' should be 0 or 1. Has: ${DOCKER_LOGS}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'DOCKER_LOGS' has correct value: ${DOCKER_LOGS}"
fi

DEVILBOX_PATH="$( get_env_value "DEVILBOX_PATH" )"
if [ ! -d "${DEVILBOX_PATH}" ]; then
    log_err "Variable 'DEVILBOX_PATH' directory does not exist: ${DEVILBOX_PATH}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'DEVILBOX_PATH' directory exists: ${DEVILBOX_PATH}"
fi

DEVILBOX_PATH_PERM="$( file_get_perm "${DEVILBOX_PATH}" )"
if [ "${DEVILBOX_PATH_PERM}" != "0755" ] && [ "${DEVILBOX_PATH_PERM}" != "0775" ] && [ "${DEVILBOX_PATH_PERM}" != "0777" ]; then
    log_err "Variable 'DEVILBOX_PATH' directory must be 0755, 0775 or 0777. Has: ${DEVILBOX_PATH_PERM}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'DEVILBOX_PATH' directory has correct permissions: ${DEVILBOX_PATH_PERM}"
fi

DEVILBOX_PATH_PERM="$( file_get_uid "${DEVILBOX_PATH}" )"
if [ "${DEVILBOX_PATH_PERM}" != "${MY_UID}" ]; then
    log_err "Variable 'DEVILBOX_PATH' directory uid must be ${MY_UID}. Has: ${DEVILBOX_PATH_PERM}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'DEVILBOX_PATH' diretory has correct uid: ${DEVILBOX_PATH_PERM}"
fi

DEVILBOX_PATH_PERM="$( file_get_gid "${DEVILBOX_PATH}" )"
if [ "${DEVILBOX_PATH_PERM}" != "${MY_GID}" ]; then
    log_err "Variable 'DEVILBOX_PATH' directory gid must be ${MY_GID}. Has: ${DEVILBOX_PATH_PERM}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'DEVILBOX_PATH' diretory has correct gid: ${DEVILBOX_PATH_PERM}"
fi

LOCAL_LISTEN_ADDR="$( get_env_value "LOCAL_LISTEN_ADDR" )"
if [ -n "${LOCAL_LISTEN_ADDR}" ]; then
    if ! echo "${LOCAL_LISTEN_ADDR}" | grep -E ':$' >/dev/null; then
        log_err "Variable 'LOCAL_LISTEN_ADDR' is not empty and missing trailing ':'"
        RET_CODE=$(( RET_CODE + 1))
        WRONG_ENV_FILES_VALUES=1
    elif ! echo "${LOCAL_LISTEN_ADDR}" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:$' >/dev/null; then
        log_err "Variable 'LOCAL_LISTEN_ADDR' has wrong value: '${LOCAL_LISTEN_ADDR}'"
        RET_CODE=$(( RET_CODE + 1))
        WRONG_ENV_FILES_VALUES=1
    else
        log_debug "Variable 'LOCAL_LISTEN_ADDR' has correct value: ${LOCAL_LISTEN_ADDR}"
    fi
else
    log_debug "Variable 'LOCAL_LISTEN_ADDR' has correct value: ${LOCAL_LISTEN_ADDR}"
fi

HOST_PATH_HTTPD_DATADIR="$( get_path "$( get_env_value "HOST_PATH_HTTPD_DATADIR" )" )"
if [ ! -d "${HOST_PATH_HTTPD_DATADIR}" ]; then
    log_err "Variable 'HOST_PATH_HTTPD_DATADIR' directory does not exist: ${HOST_PATH_HTTPD_DATADIR}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'HOST_PATH_HTTPD_DATADIR' directory exists: ${HOST_PATH_HTTPD_DATADIR}"
fi

HOST_PATH_HTTPD_DATADIR_PERM="$( file_get_perm "${HOST_PATH_HTTPD_DATADIR}" )"
if [ "${HOST_PATH_HTTPD_DATADIR_PERM}" != "0755" ] && [ "${HOST_PATH_HTTPD_DATADIR_PERM}" != "0775" ] && [ "${HOST_PATH_HTTPD_DATADIR_PERM}" != "0777" ]; then
    log_err "Variable 'HOST_PATH_HTTPD_DATADIR' directory must be 0755, 0775 or 0777. Has: ${HOST_PATH_HTTPD_DATADIR_PERM}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'HOST_PATH_HTTPD_DATADIR' directory has correct permissions: ${HOST_PATH_HTTPD_DATADIR_PERM}"
fi

HOST_PATH_HTTPD_DATADIR_PERM="$( file_get_uid "${HOST_PATH_HTTPD_DATADIR}" )"
if [ "${HOST_PATH_HTTPD_DATADIR_PERM}" != "${MY_UID}" ]; then
    log_err "Variable 'HOST_PATH_HTTPD_DATADIR' directory uid must be ${MY_UID}. Has: ${HOST_PATH_HTTPD_DATADIR_PERM}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'HOST_PATH_HTTPD_DATADIR' directory has correct uid: ${HOST_PATH_HTTPD_DATADIR_PERM}"
fi

HOST_PATH_HTTPD_DATADIR_PERM="$( file_get_gid "${HOST_PATH_HTTPD_DATADIR}" )"
if [ "${HOST_PATH_HTTPD_DATADIR_PERM}" != "${MY_GID}" ]; then
    log_err "Variable 'HOST_PATH_HTTPD_DATADIR' directory gid must be ${MY_GID}. Has: ${HOST_PATH_HTTPD_DATADIR_PERM}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'HOST_PATH_HTTPD_DATADIR' directory has correct gid: ${HOST_PATH_HTTPD_DATADIR_PERM}"
fi

PHP_SERVER="$( get_env_value "PHP_SERVER" )"
if ! grep -E "^#?PHP_SERVER=${PHP_SERVER}\$" env-example >/dev/null; then
    log_err "Variable 'PHP_SERVER' has wrong value: ${PHP_SERVER}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'PHP_SERVER' has correct value: ${PHP_SERVER}"
fi

HTTPD_SERVER="$( get_env_value "HTTPD_SERVER" )"
if ! grep -E "^#?HTTPD_SERVER=${HTTPD_SERVER}\$" env-example >/dev/null; then
    log_err "Variable 'HTTPD_SERVER' has wrong value: ${HTTPD_SERVER}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'HTTPD_SERVER' has correct value: ${HTTPD_SERVER}"
fi

MYSQL_SERVER="$( get_env_value "MYSQL_SERVER" )"
if ! grep -E "^#?MYSQL_SERVER=${MYSQL_SERVER}\$" env-example >/dev/null; then
    log_err "Variable 'MYSQL_SERVER' has wrong value: ${MYSQL_SERVER}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'MYSQL_SERVER' has correct value: ${MYSQL_SERVER}"
fi

PGSQL_SERVER="$( get_env_value "PGSQL_SERVER" )"
if ! grep -E "^#?PGSQL_SERVER=${PGSQL_SERVER}\$" env-example >/dev/null; then
    log_err "Variable 'PGSQL_SERVER' has wrong value: ${PGSQL_SERVER}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'PGSQL_SERVER' has correct value: ${PGSQL_SERVER}"
fi

REDIS_SERVER="$( get_env_value "REDIS_SERVER" )"
if ! grep -E "^#?REDIS_SERVER=${REDIS_SERVER}\$" env-example >/dev/null; then
    log_err "Variable 'REDIS_SERVER' has wrong value: ${REDIS_SERVER}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'REDIS_SERVER' has correct value: ${REDIS_SERVER}"
fi

MEMCD_SERVER="$( get_env_value "MEMCD_SERVER" )"
if ! grep -E "^#?MEMCD_SERVER=${MEMCD_SERVER}\$" env-example >/dev/null; then
    log_err "Variable 'MEMCD_SERVER' has wrong value: ${MEMCD_SERVER}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'MEMCD_SERVER' has correct value: ${MEMCD_SERVER}"
fi

MONGO_SERVER="$( get_env_value "MONGO_SERVER" )"
if ! grep -E "^#?MONGO_SERVER=${MONGO_SERVER}\$" env-example >/dev/null; then
    log_err "Variable 'MONGO_SERVER' has wrong value: ${MONGO_SERVER}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'MONGO_SERVER' has correct value: ${MONGO_SERVER}"
fi

NEW_UID="$( get_env_value "NEW_UID" )"
if [ "${NEW_UID}" != "${MY_UID}" ]; then
    log_err "Variable 'NEW_UID' has wrong value: '${NEW_UID}'. Should have: ${MY_UID}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'NEW_UID' has correct value: '${NEW_UID}'"
fi

NEW_GID="$( get_env_value "NEW_GID" )"
if [ "${NEW_GID}" != "${MY_GID}" ]; then
    log_err "Variable 'NEW_GID' has wrong value: '${NEW_GID}'. Should have: ${MY_GID}"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'NEW_GID' has correct value: '${NEW_GID}'"
fi

TLD_SUFFIX="$( get_env_value "TLD_SUFFIX" )"
TLD_SUFFIX_BLACKLIST="dev|com|org|net|int|edu|de"
if echo "${TLD_SUFFIX}" | grep -E "^(${TLD_SUFFIX_BLACKLIST})\$" >/dev/null; then
    log_err "Variable 'TLD_SUFFX' should not be set to '${TLD_SUFFIX}'. It is a real tld domain."
    log_err "All DNS requests will be intercepted to this tld domain and re-routed to the HTTP container."
    log_info "Consider using a subdomain value of e.g.: 'mydev.${TLD_SUFFIX}' instead."
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
elif [ "${TLD_SUFFIX}" = "localhost" ]; then
    log_err "Variable 'TLD_SUFFX' should not be set to '${TLD_SUFFIX}'. It is a loopback address."
    log_info "See: https://tools.ietf.org/html/draft-west-let-localhost-be-localhost-06"
    RET_CODE=$(( RET_CODE + 1))
    WRONG_ENV_FILES_VALUES=1
else
    log_debug "Variable 'TLD_SUFFIX' has correct value: '${TLD_SUFFIX}'"
fi

if [ "${WRONG_ENV_FILES_VALUES}" = "0" ]; then
    log_ok "All .env file variables have correct values"
fi

#--------------------------------------------------------------------------------------------------
# Ensure cfg/ and log/ directories exist
#--------------------------------------------------------------------------------------------------
print_head_1 "Checking required Devilbox core directories exist"

# /cfg/php-fpm-VERSION
DIR_MISSING=0
while read -r php_version; do
    if [ ! -d "cfg/php-fpm-${php_version}" ]; then
        log_err "Directory 'cfg/php-fpm-${php_version}' is missing"
        RET_CODE=$(( RET_CODE + 1))
        DIR_MISSING=1
    else
        log_debug "Directory 'cfg/php-fpm-${php_version}' is present"
    fi
done < <(grep -E '^#?PHP_SERVER=' env-example  | awk -F'=' '{print $2}')
if [ "${DIR_MISSING}" = "0" ]; then
    log_ok "All PHP cfg/ sub directories are present"
fi

# /log/php-fpm-VERSION
DIR_MISSING=0
while read -r php_version; do
    if [ ! -d "log/php-fpm-${php_version}" ]; then
        log_err "Directory 'log/php-fpm-${php_version}' is missing"
        RET_CODE=$(( RET_CODE + 1))
        DIR_MISSING=1
    else
        log_debug "Directory 'log/php-fpm-${php_version}' is present"
    fi
done < <(grep -E '^#?PHP_SERVER=' env-example  | awk -F'=' '{print $2}')
if [ "${DIR_MISSING}" = "0" ]; then
    log_ok "All PHP log/ sub directories are present"
fi

# /cfg/apache|nginx-VERSION
DIR_MISSING=0
while read -r httpd_version; do
    if [ ! -d "cfg/${httpd_version}" ]; then
        log_err "Directory 'cfg/${httpd_version}' is missing"
        RET_CODE=$(( RET_CODE + 1))
        DIR_MISSING=1
    else
        log_debug "Directory 'cfg/${httpd_version}' is present"
    fi
done < <(grep -E '^#?HTTPD_SERVER=' env-example  | awk -F'=' '{print $2}')
if [ "${DIR_MISSING}" = "0" ]; then
    log_ok "All HTTPD cfg/ sub directories are present"
fi

# /log/apache|nginx-VERSION
DIR_MISSING=0
while read -r httpd_version; do
    if [ ! -d "log/${httpd_version}" ]; then
        log_err "Directory 'log/${httpd_version}' is missing"
        RET_CODE=$(( RET_CODE + 1))
        DIR_MISSING=1
    else
        log_debug "Directory 'log/${httpd_version}' is present"
    fi
done < <(grep -E '^#?HTTPD_SERVER=' env-example  | awk -F'=' '{print $2}')
if [ "${DIR_MISSING}" = "0" ]; then
    log_ok "All HTTPD log/ sub directories are present"
fi

#--------------------------------------------------------------------------------------------------
# Devilbox Directory permissions
#--------------------------------------------------------------------------------------------------
print_head_1 "Checking devilbox core directory permissions"

DEVILBOX_DIRS=(
    "autostart"
    "bash"
    "ca"
    "cfg"
    "compose"
    "log"
    "supervisor"
)

# Check allowed directory permissions: 0755 0775 0777
DEVILBOX_DIR_PERM_WRONG=0
for search_dir in "${DEVILBOX_DIRS[@]}"; do
    while read -r my_dir; do
        PERM="$( file_get_perm "${my_dir}" )"
        if [ "${PERM}" != "0755" ] && [ "${PERM}" != "0775" ] && [ "${PERM}" != "0777" ]; then
            log_err "Directory '${my_dir}' should have 0755, 0775 or 0777 permissions. Has: ${PERM} permissions"
            RET_CODE=$(( RET_CODE + 1))
            DEVILBOX_DIR_PERM_WRONG=1
        else
            log_debug "Directory '${my_dir}' has correct permissions: ${PERM}"
        fi
    done < <(find "${search_dir}" -type d)
done
if [ "${DEVILBOX_DIR_PERM_WRONG}" = "0" ]; then
    log_ok "All devilbox directories have correct permissions"
fi

# Check allowed uid
DEVILBOX_DIR_PERM_WRONG=0
for search_dir in "${DEVILBOX_DIRS[@]}"; do
    while read -r my_dir; do
        PERM="$( file_get_uid "${my_dir}" )"
        if [ "${PERM}" != "${MY_UID}" ]; then
            log_err "Directory '${my_dir}' should have uid '${MY_UID}' Has: '${PERM}'"
            RET_CODE=$(( RET_CODE + 1))
            DEVILBOX_DIR_PERM_WRONG=1
        else
            log_debug "Directory '${my_dir}' has correct uid: ${PERM}"
        fi
    done < <(find "${search_dir}" -type d)
done
if [ "${DEVILBOX_DIR_PERM_WRONG}" = "0" ]; then
    log_ok "All devilbox directories have correct uid"
fi

# Check allowed gid
DEVILBOX_DIR_PERM_WRONG=0
for search_dir in "${DEVILBOX_DIRS[@]}"; do
    while read -r my_dir; do
        PERM="$( file_get_gid "${my_dir}" )"
        if [ "${PERM}" != "${MY_GID}" ]; then
            log_err "Directory '${my_dir}' should have gid '${MY_GID}' Has: '${PERM}'"
            RET_CODE=$(( RET_CODE + 1))
            DEVILBOX_DIR_PERM_WRONG=1
        else
            log_debug "Directory '${my_dir}' has correct gid: ${PERM}"
        fi
    done < <(find "${search_dir}" -type d)
done
if [ "${DEVILBOX_DIR_PERM_WRONG}" = "0" ]; then
    log_ok "All devilbox directories have correct gid"
fi

#--------------------------------------------------------------------------------------------------
# Devilbox File permissions
#--------------------------------------------------------------------------------------------------
print_head_1 "Checking devilbox core file permissions"

DEVILBOX_DIRS=(
    "autostart"
    "ca"
    "cfg"
    "compose"
    "supervisor"
)

# Check allowed directory permissions: 0644 0664 0666
DEVILBOX_DIR_PERM_WRONG=0
for search_file in "${DEVILBOX_DIRS[@]}"; do
    while read -r my_file; do
        PERM="$( file_get_perm "${my_file}" )"
        # Private CA file
        if [ "${my_file}" = "ca/devilbox-ca.key" ]; then
            if [ "${PERM}" != "0600" ]; then
                log_err "File '${my_file}' should have 0600 permissions. Has: ${PERM} permissions"
                RET_CODE=$(( RET_CODE + 1))
                DEVILBOX_DIR_PERM_WRONG=1
            else
                log_debug "File '${my_file}' has correct permissions: ${PERM}"
            fi
        # Executable files
        elif echo "${my_file}" | grep -E '.+\.sh(-example)?$' >/dev/null; then
            if [ "${PERM}" != "0755" ] && [ "${PERM}" != "0775" ] && [ "${PERM}" != "0777" ]; then
                log_err "File '${my_file}' should have 0755, 0775 or 0777 permissions. Has: ${PERM} permissions"
                RET_CODE=$(( RET_CODE + 1))
                DEVILBOX_DIR_PERM_WRONG=1
            else
                log_debug "File '${my_file}' has correct permissions: ${PERM}"
            fi
        # All other files
        else
            if [ "${PERM}" != "0644" ] && [ "${PERM}" != "0664" ] && [ "${PERM}" != "0666" ]; then
                log_err "File '${my_file}' should have 0644, 0664 or 0666 permissions. Has: ${PERM} permissions"
                RET_CODE=$(( RET_CODE + 1))
                DEVILBOX_DIR_PERM_WRONG=1
            else
                log_debug "File '${my_file}' has correct permissions: ${PERM}"
            fi
        fi
    done < <(find "${search_file}" -type f)
done
if [ "${DEVILBOX_DIR_PERM_WRONG}" = "0" ]; then
    log_ok "All devilbox files have correct permissions"
fi

# Check allowed uid
DEVILBOX_DIR_PERM_WRONG=0
for search_file in "${DEVILBOX_DIRS[@]}"; do
    while read -r my_file; do
        PERM="$( file_get_uid "${my_file}" )"
        if [ "${PERM}" != "${MY_UID}" ]; then
            log_err "File '${my_file}' should have uid '${MY_UID}' Has: '${PERM}'"
            RET_CODE=$(( RET_CODE + 1))
            DEVILBOX_DIR_PERM_WRONG=1
        else
            log_debug "File '${my_file}' has correct uid: ${PERM}"
        fi
    done < <(find "${search_file}" -type f)
done
if [ "${DEVILBOX_DIR_PERM_WRONG}" = "0" ]; then
    log_ok "All devilbox files have correct uid"
fi

# Check allowed gid
DEVILBOX_DIR_PERM_WRONG=0
for search_file in "${DEVILBOX_DIRS[@]}"; do
    while read -r my_file; do
        PERM="$( file_get_gid "${my_file}" )"
        if [ "${PERM}" != "${MY_GID}" ]; then
            log_err "File '${my_file}' should have gid '${MY_GID}' Has: '${PERM}'"
            RET_CODE=$(( RET_CODE + 1))
            DEVILBOX_DIR_PERM_WRONG=1
        else
            log_debug "File '${my_file}' has correct gid: ${PERM}"
        fi
    done < <(find "${search_file}" -type f)
done
if [ "${DEVILBOX_DIR_PERM_WRONG}" = "0" ]; then
    log_ok "All devilbox files have correct gid"
fi

#--------------------------------------------------------------------------------------------------
# Check projects permissions
#--------------------------------------------------------------------------------------------------
print_head_1 "Checking projects permissions"

HOST_PATH_HTTPD_DATADIR="$( get_path "$( get_env_value "HOST_PATH_HTTPD_DATADIR" )" )"

DATA_DIR_PERM_WRONG=0
while read -r project; do
    PERM="$( file_get_perm "${project}" )"
    if [ "${PERM}" != "0755" ] && [ "${PERM}" != "0775" ] && [ "${PERM}" != "0777" ]; then
        log_err "Directory '${project}' should have 0755, 0775 or 0777 permissions. Has: ${PERM} permissions"
        RET_CODE=$(( RET_CODE + 1))
        DATA_DIR_PERM_WRONG=1
    else
        log_debug "Directory '${project}' has correct permissions: ${PERM}"
    fi
done < <(get_sub_dirs_level_1 "${HOST_PATH_HTTPD_DATADIR}")
if [ "${DATA_DIR_PERM_WRONG}" = "0" ]; then
    log_ok "All project dirs have correct permissions"
fi

DATA_DIR_PERM_WRONG=0
while read -r project; do
    PERM="$( file_get_uid "${project}" )"
    if [ "${PERM}" != "${MY_UID}" ]; then
        log_err "Directory '${project}' should have uid '${MY_UID}' Has: '${PERM}'"
        RET_CODE=$(( RET_CODE + 1))
        DATA_DIR_PERM_WRONG=1
    else
        log_debug "Directory '${project}' has correct uid: ${PERM}"
    fi
done < <(get_sub_dirs_level_1 "${HOST_PATH_HTTPD_DATADIR}")
if [ "${DATA_DIR_PERM_WRONG}" = "0" ]; then
    log_ok "All project dirs have correct uid"
fi

DATA_DIR_PERM_WRONG=0
while read -r project; do
    PERM="$( file_get_gid "${project}" )"
    if [ "${PERM}" != "${MY_GID}" ]; then
        log_err "Directory '${project}' should have gid '${MY_GID}' Has: '${PERM}'"
        RET_CODE=$(( RET_CODE + 1))
        DATA_DIR_PERM_WRONG=1
    else
        log_debug "Directory '${project}' has correct gid: ${PERM}"
    fi
done < <(get_sub_dirs_level_1 "${HOST_PATH_HTTPD_DATADIR}")
if [ "${DATA_DIR_PERM_WRONG}" = "0" ]; then
    log_ok "All project dirs have correct gid"
fi

#--------------------------------------------------------------------------------------------------
# Check projects settings
#--------------------------------------------------------------------------------------------------
print_head_1 "Checking projects settings"

HOST_PATH_HTTPD_DATADIR="$( get_path "$( get_env_value "HOST_PATH_HTTPD_DATADIR" )" )"

TLD_SUFFIX="$( get_env_value "TLD_SUFFIX" )"
DNS_RECORD_WRONG=0
while read -r project; do
    VHOST="$( basename "${project}" ).${TLD_SUFFIX}"
    if ! validate_dns "${VHOST}"; then
        log_err "Project '${VHOST}' has no valid DNS record"
        RET_CODE=$(( RET_CODE + 1))
        DNS_RECORD_WRONG=1
    else
        log_debug "Project '${VHOST}' has valid DNS record"
    fi
done < <(get_sub_dirs_level_1 "${HOST_PATH_HTTPD_DATADIR}")
if [ "${DNS_RECORD_WRONG}" = "0" ]; then
    log_ok "All projects have valid DNS records"
fi

HTTPD_DOCROOT_DIR="$( get_env_value "HTTPD_DOCROOT_DIR" )"
DOCROOT_WRONG=0
while read -r project; do
    if [ ! -d "${project}/${HTTPD_DOCROOT_DIR}" ]; then
        log_err "Missing HTTPD_DOCROOT_DIR '${HTTPD_DOCROOT_DIR}' in: ${project}"
        RET_CODE=$(( RET_CODE + 1))
        DOCROOT_WRONG=1
    else
        log_debug "HTTPD_DOCROOT_DIR '${HTTPD_DOCROOT_DIR}' present in: ${project}"
    fi
done < <(get_sub_dirs_level_1 "${HOST_PATH_HTTPD_DATADIR}")
if [ "${DOCROOT_WRONG}" = "0" ]; then
    log_ok "All projects have valid HTTPD_DOCROOT_DIR"
fi

#--------------------------------------------------------------------------------------------------
# Check Customizations
#--------------------------------------------------------------------------------------------------
print_head_1 "Checking customizations"

CUSTOMIZATIONS=0

# vhost-gen
HOST_PATH_HTTPD_DATADIR="$( get_path "$( get_env_value "HOST_PATH_HTTPD_DATADIR" )" )"
HTTPD_TEMPLATE_DIR="$( get_env_value "HTTPD_TEMPLATE_DIR" )"
while read -r project; do
    if [ -f "${project}/${HTTPD_TEMPLATE_DIR}/apache22.yml" ]; then
        log_note "[vhost-gen]  Custom Apache 2.2 vhost-gen config present in: ${project}/"
        CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
    elif [ -f "${project}/${HTTPD_TEMPLATE_DIR}/apache24.yml" ]; then
        log_note "[vhost-gen]  Custom Apache 2.4 vhost-gen config present in: ${project}/"
        CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
    elif [ -f "${project}/${HTTPD_TEMPLATE_DIR}/nginx.yml" ]; then
        log_note "[vhost-gen]  Custom Nginx vhost-gen config present in: ${project}/"
        CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
    else
        log_debug "[vhost-gen]  No custom configuration for: ${project}/"
    fi
done < <(get_sub_dirs_level_1 "${HOST_PATH_HTTPD_DATADIR}")

# docker-compose.override.yml
if [ -f "docker-compose.override.yml" ]; then
    log_note "[docker]     Custom docker-compose.override.yml present"
    CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
else
    log_debug "[docker]     No custom docker-compose.override.yml present"
fi

# cfg/HTTPD/
while read -r httpd; do
    if find "cfg/${httpd}" | grep -E '\.conf$' >/dev/null; then
        log_note "[httpd]      Custom config present in cfg/${httpd}/"
        CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
    else
        log_debug "[httpd]      No custom config present in cfg/${httpd}/"
    fi
done < <(grep -E '^#?HTTPD_SERVER=' env-example  | awk -F'=' '{print $2}')

# cfg/php-ini-${version}/
while read -r php_version; do
    if find "cfg/php-ini-${php_version}" | grep -E '\.ini$' >/dev/null; then
        log_note "[php.ini]    Custom config present in cfg/php-ini-${php_version}/"
        CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
    else
        log_debug "[php.ini]    No custom config present in cfg/php-ini-${php_version}/"
    fi
done < <(grep -E '^#?PHP_SERVER=' env-example  | awk -F'=' '{print $2}')

# cfg/php-fpm-${version}/
while read -r php_version; do
    if find "cfg/php-fpm-${php_version}" | grep -E '\.conf$' >/dev/null; then
        log_note "[php-fpm]    Custom config present in cfg/php-fpm-${php_version}/"
        CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
    else
        log_debug "[php-fpm]    No custom config present in cfg/php-fpm-${php_version}/"
    fi
done < <(grep -E '^#?PHP_SERVER=' env-example  | awk -F'=' '{print $2}')

# cfg/MYSQL/
while read -r mysql; do
    if find "cfg/${mysql}" | grep -E '\.cnf$' >/dev/null; then
        log_note "[mysql]      Custom config present in cfg/${mysql}/"
        CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
    else
        log_debug "[mysql]      No custom config present in cfg/${mysql}/"
    fi
done < <(grep -E '^#?MYSQL_SERVER=' env-example  | awk -F'=' '{print $2}')

# cfg/php-startup-${version}/
while read -r php_version; do
    if find "cfg/php-startup-${php_version}" | grep -E '\.sh$' >/dev/null; then
        log_note "[startup]    Custom script present in cfg/php-startup-${php_version}/"
        CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
    else
        log_debug "[startup]    No custom script present in cfg/php-startup-${php_version}/"
    fi
done < <(grep -E '^#?PHP_SERVER=' env-example  | awk -F'=' '{print $2}')

# autostart/
if find "autostart" | grep -E '\.sh$' >/dev/null; then
    log_note "[startup]    Custom script present in autostart/"
    CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
else
    log_debug "[startup]    No custom script present in autostart/"
fi

# supervisor/
if find "supervisor" | grep -E '\.conf$' >/dev/null; then
    log_note "[supervisor] Custom config present in supervisor/"
    CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
else
    log_debug "[supervisor] No custom config present in supervisor/"
fi

# bash/
if find "bash" | grep -E '\.sh$' >/dev/null; then
    log_note "[bash]      Custom script present in bash/"
    CUSTOMIZATIONS=$(( CUSTOMIZATIONS + 1 ))
else
    log_debug "[bash]      No custom script present in bash/"
fi

# Total?
if [ "${CUSTOMIZATIONS}" = "0" ]; then
    log_info "No custom configurations applied"
fi

#--------------------------------------------------------------------------------------------------
# Summary
#--------------------------------------------------------------------------------------------------
print_head_1 "SUMMARY"

if [ "${RET_CODE}" -gt "0" ]; then
    log_err "Found ${RET_CODE} error(s)"
    log_err "Devilbox might not work properly"
    log_err "Fix the issues before submitting a bug report"
    if [ "${CUSTOMIZATIONS}" -gt "0" ]; then
        log_note "${CUSTOMIZATIONS} custom configurations applied. If you encounter issues, reset them first."
    else
        log_info "No custom configurations applied"
    fi
    log_info "Ensure to run 'docker-compose stop; docker-compose rm -f' on .env changes or custom configs"
    exit 1
else
    log_ok "Found no errors"
    if [ "${CUSTOMIZATIONS}" -gt "0" ]; then
        log_note "${CUSTOMIZATIONS} custom configurations applied. If you encounter issues, reset them first."
    else
        log_info "No custom configurations applied"
    fi
    log_info "Ensure to run 'docker-compose stop; docker-compose rm -f' on .env changes or custom configs"
    exit 0
fi

Log: docker-compose logs

Traceback (most recent call last):
  File "/usr/lib/python3.12/site-packages/urllib3/connectionpool.py", line 715, in urlopen
    httplib_response = self._make_request(
                       ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/urllib3/connectionpool.py", line 416, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "/usr/lib/python3.12/site-packages/urllib3/connection.py", line 244, in request
    super(HTTPConnection, self).request(method, url, body=body, headers=headers)
  File "/usr/lib64/python3.12/http/client.py", line 1336, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/usr/lib64/python3.12/http/client.py", line 1382, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/usr/lib64/python3.12/http/client.py", line 1331, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib64/python3.12/http/client.py", line 1091, in _send_output
    self.send(msg)
  File "/usr/lib64/python3.12/http/client.py", line 1035, in send
    self.connect()
  File "/usr/lib/python3.12/site-packages/docker/transport/unixconn.py", line 27, in connect
    sock.connect(self.unix_socket)
PermissionError: [Errno 13] Permission denied

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.12/site-packages/requests/adapters.py", line 486, in send
    resp = conn.urlopen(
           ^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/urllib3/connectionpool.py", line 799, in urlopen
    retries = retries.increment(
              ^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/urllib3/util/retry.py", line 550, in increment
    raise six.reraise(type(error), error, _stacktrace)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/urllib3/packages/six.py", line 769, in reraise
    raise value.with_traceback(tb)
  File "/usr/lib/python3.12/site-packages/urllib3/connectionpool.py", line 715, in urlopen
    httplib_response = self._make_request(
                       ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/urllib3/connectionpool.py", line 416, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "/usr/lib/python3.12/site-packages/urllib3/connection.py", line 244, in request
    super(HTTPConnection, self).request(method, url, body=body, headers=headers)
  File "/usr/lib64/python3.12/http/client.py", line 1336, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/usr/lib64/python3.12/http/client.py", line 1382, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/usr/lib64/python3.12/http/client.py", line 1331, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib64/python3.12/http/client.py", line 1091, in _send_output
    self.send(msg)
  File "/usr/lib64/python3.12/http/client.py", line 1035, in send
    self.connect()
  File "/usr/lib/python3.12/site-packages/docker/transport/unixconn.py", line 27, in connect
    sock.connect(self.unix_socket)
urllib3.exceptions.ProtocolError: ('Connection aborted.', PermissionError(13, 'Permission denied'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.12/site-packages/docker/api/client.py", line 214, in _retrieve_server_version
    return self.version(api_version=False)["ApiVersion"]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/docker/api/daemon.py", line 181, in version
    return self._result(self._get(url), json=True)
                        ^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/docker/utils/decorators.py", line 46, in inner
    return f(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/docker/api/client.py", line 237, in _get
    return self.get(url, **self._set_request_timeout(kwargs))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/requests/sessions.py", line 602, in get
    return self.request("GET", url, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/requests/sessions.py", line 589, in request
    resp = self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/requests/sessions.py", line 703, in send
    r = adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/requests/adapters.py", line 501, in send
    raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', PermissionError(13, 'Permission denied'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/bin/docker-compose", line 33, in <module>
    sys.exit(load_entry_point('docker-compose==1.29.2', 'console_scripts', 'docker-compose')())
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/compose/cli/main.py", line 81, in main
    command_func()
  File "/usr/lib/python3.12/site-packages/compose/cli/main.py", line 200, in perform_command
    project = project_from_options('.', options)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/compose/cli/command.py", line 60, in project_from_options
    return get_project(
           ^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/compose/cli/command.py", line 152, in get_project
    client = get_client(
             ^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/compose/cli/docker_client.py", line 41, in get_client
    client = docker_client(
             ^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/compose/cli/docker_client.py", line 170, in docker_client
    client = APIClient(use_ssh_client=not use_paramiko_ssh, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/docker/api/client.py", line 197, in __init__
    self._version = self._retrieve_server_version()
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/docker/api/client.py", line 221, in _retrieve_server_version
    raise DockerException(
docker.errors.DockerException: Error while fetching server API version: ('Connection aborted.', PermissionError(13, 'Permission denied'))

(Optional) Additional information

No response