2017-11-22 00:19:14 +01:00
#!/bin/bash
2020-03-23 08:20:46 +01:00
# Convert argument to lowercase (bash 4 only)
function lc {
echo " ${ @,, } "
}
2018-12-31 12:53:21 +01:00
DEBUG = " $( lc " ${ DEBUG :- } " ) "
if [ [ " $DEBUG " = = true ] ] ; then
DEBUG = 1 && export DEBUG
fi
2017-11-22 00:19:14 +01:00
2021-12-10 22:24:11 +01:00
function parse_true( ) {
case " $1 " in
true | True | TRUE | 1)
return 0
; ;
*)
return 1
; ;
esac
}
2022-12-06 12:28:48 +01:00
function in_array( ) {
local needle = " $1 " item
local -n arrref = " $2 "
for item in " ${ arrref [@] } " ; do
[ [ " $item " = = " $needle " ] ] && return 0
done
return 1
}
2016-01-07 11:11:05 +01:00
[ [ -z " ${ VHOST_DIR :- } " ] ] && \
declare -r VHOST_DIR = /etc/nginx/vhost.d
[ [ -z " ${ START_HEADER :- } " ] ] && \
declare -r START_HEADER = '## Start of configuration add by letsencrypt container'
[ [ -z " ${ END_HEADER :- } " ] ] && \
declare -r END_HEADER = '## End of configuration add by letsencrypt container'
2016-01-01 14:32:40 +01:00
2018-02-01 12:23:54 +01:00
function check_nginx_proxy_container_run {
2020-03-23 08:20:46 +01:00
local _nginx_proxy_container; _nginx_proxy_container = $( get_nginx_proxy_container)
2018-06-05 09:44:16 +02:00
if [ [ -n " $_nginx_proxy_container " ] ] ; then
if [ [ $( docker_api " /containers/ ${ _nginx_proxy_container } /json " | jq -r '.State.Status' ) = "running" ] ] ; then
return 0
else
echo " $( date "+%Y/%m/%d %T" ) Error: nginx-proxy container ${ _nginx_proxy_container } isn't running. " >& 2
return 1
fi
else
echo " $( date "+%Y/%m/%d %T" ) Error: could not get a nginx-proxy container ID. " >& 2
return 1
fi
2018-02-01 12:23:54 +01:00
}
2019-08-02 17:36:58 +02:00
function ascending_wildcard_locations {
# Given foo.bar.baz.example.com as argument, will output:
# - *.bar.baz.example.com
# - *.baz.example.com
# - *.example.com
local domain = " ${ 1 : ? } "
local first_label
2023-11-23 13:38:42 +01:00
tld_regex = " ^[[:alpha:]]+ $"
regex = " ^[^.]+\..+ $"
while [ [ " $domain " = ~ $regex ] ] ; do
2019-08-02 17:36:58 +02:00
first_label = " ${ domain %%.* } "
2023-11-23 13:38:42 +01:00
domain = " ${ domain /# " ${ first_label } . " / } "
if [ [ " $domain " = = "*" || " $domain " = ~ $tld_regex ] ] ; then
2021-08-03 23:16:36 +02:00
return
else
echo " *. ${ domain } "
fi
2019-08-02 17:36:58 +02:00
done
}
function descending_wildcard_locations {
# Given foo.bar.baz.example.com as argument, will output:
# - foo.bar.baz.example.*
# - foo.bar.baz.*
# - foo.bar.*
# - foo.*
local domain = " ${ 1 : ? } "
local last_label
2023-11-23 13:38:42 +01:00
regex = " ^.+\.[^.]+ $"
while [ [ " $domain " = ~ $regex ] ] ; do
2019-08-02 17:36:58 +02:00
last_label = " ${ domain ##*. } "
2023-11-23 13:38:42 +01:00
domain = " ${ domain /% " . ${ last_label } " / } "
if [ [ " $domain " = = "*" ] ] ; then
2021-08-03 23:16:36 +02:00
return
else
echo " ${ domain } .* "
fi
2019-08-02 17:36:58 +02:00
done
}
function enumerate_wildcard_locations {
# Goes through ascending then descending wildcard locations for a given FQDN
local domain = " ${ 1 : ? } "
ascending_wildcard_locations " $domain "
descending_wildcard_locations " $domain "
}
2018-02-08 23:57:50 +01:00
function add_location_configuration {
2016-01-07 11:11:05 +01:00
local domain = " ${ 1 :- } "
2019-08-02 17:36:58 +02:00
local wildcard_domain
# If no domain was passed use default instead
[ [ -z " $domain " ] ] && domain = 'default'
# If the domain does not have an exact matching location file, test the possible
# wildcard locations files. Use default is no location file is present at all.
if [ [ ! -f " ${ VHOST_DIR } / ${ domain } " ] ] ; then
2021-03-15 05:44:46 +01:00
while read -r wildcard_domain; do
2019-08-02 17:36:58 +02:00
if [ [ -f " ${ VHOST_DIR } / ${ wildcard_domain } " ] ] ; then
domain = " $wildcard_domain "
break
fi
domain = 'default'
2021-03-15 05:44:46 +01:00
done <<< " $( enumerate_wildcard_locations " $domain " ) "
2019-08-02 17:36:58 +02:00
fi
2019-06-09 13:41:15 +02:00
if [ [ -f " ${ VHOST_DIR } / ${ domain } " && -n $( sed -n " / $START_HEADER /,/ $END_HEADER /p " " ${ VHOST_DIR } / ${ domain } " ) ] ] ; then
# If the config file exist and already have the location configuration, end with exit code 0
return 0
else
# Else write the location configuration to a temp file ...
echo " $START_HEADER " > " ${ VHOST_DIR } / ${ domain } " .new
cat /app/nginx_location.conf >> " ${ VHOST_DIR } / ${ domain } " .new
echo " $END_HEADER " >> " ${ VHOST_DIR } / ${ domain } " .new
# ... append the existing file content to the temp one ...
[ [ -f " ${ VHOST_DIR } / ${ domain } " ] ] && cat " ${ VHOST_DIR } / ${ domain } " >> " ${ VHOST_DIR } / ${ domain } " .new
# ... and copy the temp file to the old one (if the destination file is bind mounted, you can't change
# its inode from within the container, so mv won't work and cp has to be used), then remove the temp file.
cp -f " ${ VHOST_DIR } / ${ domain } " .new " ${ VHOST_DIR } / ${ domain } " && rm -f " ${ VHOST_DIR } / ${ domain } " .new
return 1
fi
2016-01-01 14:32:40 +01:00
}
2018-06-25 11:16:53 +02:00
function add_standalone_configuration {
local domain = " ${ 1 : ? } "
2020-11-12 18:04:53 +01:00
if grep -q " server_name ${ domain } ; " /etc/nginx/conf.d/*.conf; then
2018-06-25 11:16:53 +02:00
# If the domain is already present in nginx's conf, use the location configuration.
add_location_configuration " $domain "
else
# Else use the standalone configuration.
cat > " /etc/nginx/conf.d/standalone-cert- $domain .conf " << EOF
server {
server_name $domain ;
listen 80;
access_log /var/log/nginx/access.log vhost;
location ^~ /.well-known/acme-challenge/ {
auth_basic off;
2020-05-11 15:47:00 +02:00
auth_request off;
2018-06-25 11:16:53 +02:00
allow all;
root /usr/share/nginx/html;
try_files \$ uri = 404;
break;
}
}
EOF
fi
}
function remove_all_standalone_configurations {
2020-05-11 15:43:07 +02:00
local old_shopt_options; old_shopt_options = $( shopt -p) # Backup shopt options
2018-06-25 11:16:53 +02:00
shopt -s nullglob
for file in "/etc/nginx/conf.d/standalone-cert-" *".conf" ; do
rm -f " $file "
done
eval " $old_shopt_options " # Restore shopt options
}
2018-02-08 23:57:50 +01:00
function remove_all_location_configurations {
2016-01-01 14:32:40 +01:00
for file in " ${ VHOST_DIR } " /*; do
2019-06-09 13:41:15 +02:00
[ [ -e " $file " ] ] || continue
if [ [ -n $( sed -n " / $START_HEADER /,/ $END_HEADER /p " " $file " ) ] ] ; then
sed " / $START_HEADER /,/ $END_HEADER /d " " $file " > " $file " .new
cp -f " $file " .new " $file " && rm -f " $file " .new
fi
2016-01-01 14:32:40 +01:00
done
}
2016-01-05 14:02:15 +01:00
2018-08-01 15:07:32 +02:00
function check_cert_min_validity {
# Check if a certificate ($1) is still valid for a given amount of time in seconds ($2).
# Returns 0 if the certificate is still valid for this amount of time, 1 otherwise.
local cert_path = " $1 "
local min_validity = " $(( $( date "+%s" ) + $2 )) "
local cert_expiration
cert_expiration = " $( openssl x509 -noout -enddate -in " $cert_path " | cut -d "=" -f 2) "
cert_expiration = " $( date --utc --date " ${ cert_expiration % GMT } " "+%s" ) "
[ [ $cert_expiration -gt $min_validity ] ] || return 1
}
2018-05-20 18:15:10 +02:00
function get_self_cid {
2019-05-17 18:01:59 +02:00
local self_cid = ""
2019-01-16 11:29:24 +01:00
# Try the /proc files methods first then resort to the Docker API.
if [ [ -f /proc/1/cpuset ] ] ; then
2024-05-04 12:41:47 +02:00
self_cid = " $( grep -Eo -m 1 '[[:alnum:]]{64}' /proc/1/cpuset) "
2019-02-28 10:47:14 +01:00
fi
if [ [ ( ${# self_cid } != 64 ) && ( -f /proc/self/cgroup ) ] ] ; then
self_cid = " $( grep -Eo -m 1 '[[:alnum:]]{64}' /proc/self/cgroup) "
fi
2024-01-28 03:32:37 +01:00
# cgroups v2
if [ [ ( ${# self_cid } != 64 ) && ( -f /proc/self/mountinfo ) ] ] ; then
2024-05-04 12:41:47 +02:00
self_cid = " $( grep '/userdata/hostname' /proc/self/mountinfo | grep -Eo -m 1 '[[:alnum:]]{64}' ) "
2024-01-28 03:32:37 +01:00
fi
2019-02-28 10:47:14 +01:00
if [ [ ( ${# self_cid } != 64 ) ] ] ; then
2019-01-16 11:29:24 +01:00
self_cid = " $( docker_api " /containers/ $( hostname) /json " | jq -r '.Id' ) "
fi
# If it's not 64 characters long, then it's probably not a container ID.
if [ [ ${# self_cid } = = 64 ] ] ; then
2018-10-30 19:02:50 +01:00
echo " $self_cid "
else
echo " $( date "+%Y/%m/%d %T" ) , Error: can't get my container ID ! " >& 2
return 1
fi
2018-05-20 18:15:10 +02:00
}
2016-02-11 21:18:20 +01:00
## Docker API
2016-01-06 19:33:16 +01:00
function docker_api {
local scheme
local curl_opts = ( -s)
local method = ${ 2 :- GET }
# data to POST
if [ [ -n " ${ 3 :- } " ] ] ; then
curl_opts += ( -d " $3 " )
fi
2016-02-11 21:18:20 +01:00
if [ [ -z " $DOCKER_HOST " ] ] ; then
echo "Error DOCKER_HOST variable not set" >& 2
return 1
fi
2016-01-06 19:33:16 +01:00
if [ [ $DOCKER_HOST = = unix://* ] ] ; then
2020-03-23 08:20:46 +01:00
curl_opts += ( --unix-socket " ${ DOCKER_HOST #unix : // } " )
2017-01-19 21:14:10 +01:00
scheme = 'http://localhost'
2016-01-06 19:33:16 +01:00
else
2016-01-08 14:31:45 +01:00
scheme = " http:// ${ DOCKER_HOST #* : // } "
2016-01-06 19:33:16 +01:00
fi
[ [ $method = "POST" ] ] && curl_opts += ( -H 'Content-Type: application/json' )
2020-03-23 08:20:46 +01:00
curl " ${ curl_opts [@] } " -X " ${ method } " " ${ scheme } $1 "
2016-01-06 19:33:16 +01:00
}
function docker_exec {
local id = " ${ 1 ?missing id } "
local cmd = " ${ 2 ?missing command } "
2020-03-23 08:20:46 +01:00
local data; data = $( printf '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Tty":false,"Cmd": %s }' " $cmd " )
2016-01-06 19:33:16 +01:00
exec_id = $( docker_api " /containers/ $id /exec " "POST" " $data " | jq -r .Id)
2018-02-01 12:23:54 +01:00
if [ [ -n " $exec_id " && " $exec_id " != "null" ] ] ; then
2020-03-23 08:20:46 +01:00
docker_api " /exec/ ${ exec_id } /start " "POST" '{"Detach": false, "Tty":false}'
2018-02-01 12:23:54 +01:00
else
echo " $( date "+%Y/%m/%d %T" ) , Error: can't exec command ${ cmd } in container ${ id } . Check if the container is running. " >& 2
return 1
2016-01-06 19:33:16 +01:00
fi
}
2018-12-19 13:02:22 +01:00
function docker_restart {
local id = " ${ 1 ?missing id } "
docker_api " /containers/ $id /restart " "POST"
}
2016-02-11 21:18:20 +01:00
function docker_kill {
local id = " ${ 1 ?missing id } "
local signal = " ${ 2 ?missing signal } "
docker_api " /containers/ $id /kill?signal= $signal " "POST"
}
2017-07-13 12:44:02 +02:00
function labeled_cid {
2020-03-23 08:20:46 +01:00
docker_api "/containers/json" | jq -r '.[] | select(.Labels["' " $1 " '"])|.Id'
2017-07-13 12:44:02 +02:00
}
2018-02-09 10:38:44 +01:00
function is_docker_gen_container {
local id = " ${ 1 ?missing id } "
2020-03-23 08:20:46 +01:00
if [ [ $( docker_api " /containers/ $id /json " | jq -r '.Config.Env[]' | grep -c -E '^DOCKER_GEN_VERSION=' ) = "1" ] ] ; then
2018-02-09 10:38:44 +01:00
return 0
else
return 1
fi
}
2018-01-06 17:36:37 +01:00
function get_docker_gen_container {
2018-02-09 00:27:14 +01:00
# First try to get the docker-gen container ID from the container label.
2023-08-01 21:45:39 +02:00
local legacy_docker_gen_cid; legacy_docker_gen_cid = " $( labeled_cid com.github.jrcs.letsencrypt_nginx_proxy_companion.docker_gen) "
local new_docker_gen_cid; new_docker_gen_cid = " $( labeled_cid com.github.nginx-proxy.docker-gen) "
local docker_gen_cid; docker_gen_cid = " ${ new_docker_gen_cid :- $legacy_docker_gen_cid } "
2018-02-09 00:27:14 +01:00
# If the labeled_cid function dit not return anything and the env var is set, use it.
if [ [ -z " $docker_gen_cid " ] ] && [ [ -n " ${ NGINX_DOCKER_GEN_CONTAINER :- } " ] ] ; then
docker_gen_cid = " $NGINX_DOCKER_GEN_CONTAINER "
fi
# If a container ID was found, output it. The function will return 1 otherwise.
[ [ -n " $docker_gen_cid " ] ] && echo " $docker_gen_cid "
2017-07-13 12:44:02 +02:00
}
2018-01-06 17:36:37 +01:00
function get_nginx_proxy_container {
2018-02-09 00:27:14 +01:00
local volumes_from
# First try to get the nginx container ID from the container label.
2023-08-01 21:45:39 +02:00
local legacy_nginx_cid; legacy_nginx_cid = " $( labeled_cid com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy) "
local new_nginx_cid; new_nginx_cid = " $( labeled_cid com.github.nginx-proxy.nginx) "
local nginx_cid; nginx_cid = " ${ new_nginx_cid :- $legacy_nginx_cid } "
2018-02-09 00:27:14 +01:00
# If the labeled_cid function dit not return anything ...
if [ [ -z " ${ nginx_cid } " ] ] ; then
# ... and the env var is set, use it ...
if [ [ -n " ${ NGINX_PROXY_CONTAINER :- } " ] ] ; then
nginx_cid = " $NGINX_PROXY_CONTAINER "
# ... else try to get the container ID with the volumes_from method.
2018-10-30 19:02:50 +01:00
elif [ [ $( get_self_cid) ] ] ; then
volumes_from = $( docker_api " /containers/ $( get_self_cid) /json " | jq -r '.HostConfig.VolumesFrom[]' 2>/dev/null)
2018-02-09 00:27:14 +01:00
for cid in $volumes_from ; do
cid = " ${ cid % : * } " # Remove leading :ro or :rw set by remote docker-compose (thx anoopr)
2020-03-23 08:20:46 +01:00
if [ [ $( docker_api " /containers/ $cid /json " | jq -r '.Config.Env[]' | grep -c -E '^NGINX_VERSION=' ) = "1" ] ] ; then
2018-02-09 00:27:14 +01:00
nginx_cid = " $cid "
break
fi
done
fi
fi
# If a container ID was found, output it. The function will return 1 otherwise.
[ [ -n " $nginx_cid " ] ] && echo " $nginx_cid "
2017-07-13 12:44:02 +02:00
}
2016-01-06 19:33:16 +01:00
## Nginx
2018-02-08 23:57:50 +01:00
function reload_nginx {
2020-03-23 08:20:46 +01:00
local _docker_gen_container; _docker_gen_container = $( get_docker_gen_container)
local _nginx_proxy_container; _nginx_proxy_container = $( get_nginx_proxy_container)
2017-07-13 12:44:02 +02:00
if [ [ -n " ${ _docker_gen_container :- } " ] ] ; then
2017-05-28 17:30:10 +02:00
# Using docker-gen and nginx in separate container
2017-07-13 12:44:02 +02:00
echo " Reloading nginx docker-gen (using separate container ${ _docker_gen_container } )... "
docker_kill " ${ _docker_gen_container } " SIGHUP
if [ [ -n " ${ _nginx_proxy_container :- } " ] ] ; then
2017-05-28 17:30:10 +02:00
# Reloading nginx in case only certificates had been renewed
2017-07-13 12:44:02 +02:00
echo " Reloading nginx (using separate container ${ _nginx_proxy_container } )... "
docker_kill " ${ _nginx_proxy_container } " SIGHUP
2017-05-28 17:30:10 +02:00
fi
2016-02-11 21:18:20 +01:00
else
2017-07-13 12:44:02 +02:00
if [ [ -n " ${ _nginx_proxy_container :- } " ] ] ; then
echo " Reloading nginx proxy ( ${ _nginx_proxy_container } )... "
docker_exec " ${ _nginx_proxy_container } " \
2018-07-08 14:10:10 +02:00
'[ "sh", "-c", "/app/docker-entrypoint.sh /usr/local/bin/docker-gen /app/nginx.tmpl /etc/nginx/conf.d/default.conf; /usr/sbin/nginx -s reload" ]' \
| sed -rn 's/^.*([0-9]{4}\/[0-9]{2}\/[0-9]{2}.*$)/\1/p'
[ [ ${ PIPESTATUS [0] } -eq 1 ] ] && echo " $( date "+%Y/%m/%d %T" ) , Error: can't reload nginx-proxy. " >& 2
2016-02-11 21:18:20 +01:00
fi
2016-01-05 14:02:15 +01:00
fi
}
2016-03-27 16:44:02 +02:00
2018-10-06 22:39:01 +02:00
function set_ownership_and_permissions {
2018-09-02 21:07:49 +02:00
local path = " ${ 1 : ? } "
2023-03-27 19:03:21 +02:00
# The default ownership is root:root, with 755 permissions for folders and 600 for private files.
2018-10-06 22:39:01 +02:00
local user = " ${ FILES_UID :- root } "
local group = " ${ FILES_GID :- $user } "
2023-03-27 19:03:21 +02:00
local f_perms = " ${ FILES_PERMS :- 600 } "
2018-10-07 10:16:22 +02:00
local d_perms = " ${ FOLDERS_PERMS :- 755 } "
2018-09-02 21:07:49 +02:00
2018-10-06 22:39:01 +02:00
if [ [ ! " $f_perms " = ~ ^[ 0-7] { 3,4} $ ] ] ; then
echo " Warning : the provided files permission octal ( $f_perms ) is incorrect. Skipping ownership and permissions check. "
return 1
fi
if [ [ ! " $d_perms " = ~ ^[ 0-7] { 3,4} $ ] ] ; then
echo " Warning : the provided folders permission octal ( $d_perms ) is incorrect. Skipping ownership and permissions check. "
return 1
fi
2018-12-31 12:53:21 +01:00
[ [ " $DEBUG " = = 1 ] ] && echo " Debug: checking $path ownership and permissions. "
2019-10-08 21:26:05 +02:00
2018-10-06 22:39:01 +02:00
# Find the user numeric ID if the FILES_UID environment variable isn't numeric.
if [ [ " $user " = ~ ^[ 0-9] +$ ] ] ; then
user_num = " $user "
# Check if this user exist inside the container
elif id -u " $user " > /dev/null 2>& 1; then
# Convert the user name to numeric ID
2020-03-23 08:20:46 +01:00
local user_num; user_num = " $( id -u " $user " ) "
2018-12-31 12:53:21 +01:00
[ [ " $DEBUG " = = 1 ] ] && echo " Debug: numeric ID of user $user is $user_num . "
2018-10-06 22:39:01 +02:00
else
echo " Warning: user $user not found in the container, please use a numeric user ID instead of a user name. Skipping ownership and permissions check. "
return 1
fi
# Find the group numeric ID if the FILES_GID environment variable isn't numeric.
if [ [ " $group " = ~ ^[ 0-9] +$ ] ] ; then
group_num = " $group "
# Check if this group exist inside the container
elif getent group " $group " > /dev/null 2>& 1; then
# Convert the group name to numeric ID
2020-03-23 08:20:46 +01:00
local group_num; group_num = " $( getent group " $group " | awk -F ':' '{print $3}' ) "
2018-12-31 12:53:21 +01:00
[ [ " $DEBUG " = = 1 ] ] && echo " Debug: numeric ID of group $group is $group_num . "
2018-10-06 22:39:01 +02:00
else
echo " Warning: group $group not found in the container, please use a numeric group ID instead of a group name. Skipping ownership and permissions check. "
return 1
fi
# Check and modify ownership if required.
2018-10-13 14:09:29 +02:00
if [ [ -e " $path " ] ] ; then
if [ [ " $( stat -c %u:%g " $path " ) " != " $user_num : $group_num " ] ] ; then
2018-12-31 12:53:21 +01:00
[ [ " $DEBUG " = = 1 ] ] && echo " Debug: setting $path ownership to $user : $group . "
2018-12-14 15:01:27 +01:00
if [ [ -L " $path " ] ] ; then
chown -h " $user_num : $group_num " " $path "
else
chown " $user_num : $group_num " " $path "
fi
2018-09-02 21:07:49 +02:00
fi
2018-12-14 15:01:27 +01:00
# If the path is a folder, check and modify permissions if required.
if [ [ -d " $path " ] ] ; then
if [ [ " $( stat -c %a " $path " ) " != " $d_perms " ] ] ; then
2018-12-31 12:53:21 +01:00
[ [ " $DEBUG " = = 1 ] ] && echo " Debug: setting $path permissions to $d_perms . "
2018-12-14 15:01:27 +01:00
chmod " $d_perms " " $path "
2018-10-16 16:48:52 +02:00
fi
2018-12-14 15:01:27 +01:00
# If the path is a file, check and modify permissions if required.
elif [ [ -f " $path " ] ] ; then
2019-10-08 21:26:05 +02:00
# Use different permissions for private files (private keys and ACME account files) ...
2023-03-27 19:03:21 +02:00
if [ [ " $path " = ~ ^.*( key\. pem| \. key) $ ] ] ; then
2018-12-14 15:01:27 +01:00
if [ [ " $( stat -c %a " $path " ) " != " $f_perms " ] ] ; then
2018-12-31 12:53:21 +01:00
[ [ " $DEBUG " = = 1 ] ] && echo " Debug: setting $path permissions to $f_perms . "
2018-12-14 15:01:27 +01:00
chmod " $f_perms " " $path "
fi
# ... and for public files (certificates, chains, fullchains, DH parameters).
else
if [ [ " $( stat -c %a " $path " ) " != "644" ] ] ; then
2018-12-31 12:53:21 +01:00
[ [ " $DEBUG " = = 1 ] ] && echo " Debug: setting $path permissions to 644. "
2018-12-14 15:01:27 +01:00
chmod "644" " $path "
fi
2018-10-16 16:48:52 +02:00
fi
2018-09-02 21:07:49 +02:00
fi
2018-12-14 15:01:27 +01:00
else
echo " Warning: $path does not exist. Skipping ownership and permissions check. "
return 1
2018-09-02 21:07:49 +02:00
fi
}