From ed65f0d785df50cfc9bef590aca8126d85f68ca2 Mon Sep 17 00:00:00 2001 From: "Buster \"Silver Eagle\" Neece" Date: Tue, 18 Jan 2022 02:49:08 -0600 Subject: [PATCH] LetsEncrypt/Nginx Container Consolidation (#4995) --- Dockerfile | 4 - config/cli.php | 1 + config/events.php | 1 - docker-compose.dev.yml | 13 -- docker-compose.multisite.yml | 40 ++++ docker-compose.sample.yml | 178 +++++++--------- docker.sh | 2 - src/Console/Command/Internal/OnSslRenewal.php | 46 +++++ src/Radio/CertificateLocator.php | 15 +- .../Task/ReloadFrontendAfterSslChangeTask.php | 61 ------ util/docker/web/nginx/azuracast.conf.tmpl | 18 ++ util/docker/web/runit/acme/run | 190 ++++++++++++++++++ util/docker/web/runit/beanstalkd/run | 2 +- util/docker/web/runit/nginx/run | 2 +- util/docker/web/runit/php-fpm/run | 2 +- util/docker/web/runit/php-nowplaying/run | 2 +- util/docker/web/runit/php-worker/run | 2 +- util/docker/web/runit/sftpgo/run | 2 +- util/docker/web/scripts/azuracast_cli | 5 +- util/docker/web/scripts/azuracast_install | 2 +- util/docker/web/scripts/azuracast_restore | 2 +- util/docker/web/scripts/azuracast_sftp_auth | 2 +- util/docker/web/scripts/azuracast_sftp_event | 2 +- util/docker/web/scripts/azuracast_update | 2 +- util/docker/web/scripts/cron_task | 2 +- util/docker/web/scripts/docker_installer | 2 +- util/docker/web/scripts/on_ssl_renewal | 5 + util/docker/web/scripts/setuser | 61 ------ util/docker/web/scripts/temp_cleanup | 4 +- util/docker/web/setup/acme_sh.sh | 20 ++ .../web/startup_scripts/01_self_signed_ssl.sh | 12 ++ 31 files changed, 429 insertions(+), 273 deletions(-) create mode 100644 docker-compose.multisite.yml create mode 100644 src/Console/Command/Internal/OnSslRenewal.php delete mode 100644 src/Sync/Task/ReloadFrontendAfterSslChangeTask.php create mode 100644 util/docker/web/runit/acme/run create mode 100644 util/docker/web/scripts/on_ssl_renewal delete mode 100644 util/docker/web/scripts/setuser create mode 100644 util/docker/web/setup/acme_sh.sh create mode 100644 util/docker/web/startup_scripts/01_self_signed_ssl.sh diff --git a/Dockerfile b/Dockerfile index 17646d1b7..49e3e4d62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,10 +51,6 @@ USER root EXPOSE 80 2022 -# Nginx Proxy environment variables. -ENV VIRTUAL_HOST="azuracast.local" \ - HTTPS_METHOD="noredirect" - # Sensible default environment variables. ENV LANG="en_US.UTF-8" \ APPLICATION_ENV="production" \ diff --git a/config/cli.php b/config/cli.php index 042aca4ad..dba9f0214 100644 --- a/config/cli.php +++ b/config/cli.php @@ -14,6 +14,7 @@ return function (App\Event\BuildConsoleCommands $event) { 'azuracast:internal:sftp-event' => Command\Internal\SftpEventCommand::class, 'azuracast:internal:sftp-auth' => Command\Internal\SftpAuthCommand::class, 'azuracast:internal:nextsong' => Command\Internal\NextSongCommand::class, + 'azuracast:internal:on-ssl-renewal' => Command\Internal\OnSslRenewal::class, 'azuracast:internal:ip' => Command\Internal\GetIpCommand::class, 'azuracast:locale:generate' => Command\Locale\GenerateCommand::class, 'azuracast:locale:import' => Command\Locale\ImportCommand::class, diff --git a/config/events.php b/config/events.php index 2d6361553..33eab9c13 100644 --- a/config/events.php +++ b/config/events.php @@ -138,7 +138,6 @@ return function (CallableEventDispatcherInterface $dispatcher) { App\Sync\Task\CleanupStorageTask::class, App\Sync\Task\MoveBroadcastsTask::class, App\Sync\Task\ReactivateStreamerTask::class, - App\Sync\Task\ReloadFrontendAfterSslChangeTask::class, App\Sync\Task\RotateLogsTask::class, App\Sync\Task\RunAnalyticsTask::class, App\Sync\Task\RunAutomatedAssignmentTask::class, diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a3d36cf5f..5a4bf8b69 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,17 +1,4 @@ services : - nginx_proxy : - build : - context : ../docker-azuracast-nginx-proxy - volumes : - - ./util/local_ssl:/etc/nginx/certs - - /var/run/docker.sock:/tmp/docker.sock:ro - - nginx_proxy_letsencrypt : - build : - context : ../docker-azuracast-nginx-proxy-letsencrypt - volumes : - - /var/run/docker.sock:/tmp/docker.sock:ro - web : build : context : . diff --git a/docker-compose.multisite.yml b/docker-compose.multisite.yml new file mode 100644 index 000000000..a280ff6b7 --- /dev/null +++ b/docker-compose.multisite.yml @@ -0,0 +1,40 @@ +services: + web: + environment: + VIRTUAL_HOST: ${LETSENCRYPT_HOST:-azuracast.local} + HTTPS_METHOD: noredirect + + nginx_proxy: + container_name: nginx_proxy + image: "ghcr.io/azuracast/nginx_proxy:${AZURACAST_VERSION:-latest}" + ports: + - '80:80' + - '443:443' + volumes: + - letsencrypt:/etc/nginx/certs + - nginx_proxy_vhosts:/etc/nginx/vhost.d + - letsencrypt_html:/usr/share/nginx/html + - /var/run/docker.sock:/tmp/docker.sock:ro + environment: + NGINX_TIMEOUT: ${NGINX_TIMEOUT:-1800} + DEFAULT_HOST: ${LETSENCRYPT_HOST:-azuracast.local} + networks: + - frontend + depends_on: + - web + restart: unless-stopped + + nginx_proxy_letsencrypt: + container_name: nginx_proxy_letsencrypt + image: "ghcr.io/azuracast/nginx_proxy_letsencrypt:${AZURACAST_VERSION:-latest}" + volumes_from: + - nginx_proxy + volumes: + - letsencrypt_acme:/etc/acme.sh + - /var/run/docker.sock:/var/run/docker.sock:ro + restart: always + +volumes: + letsencrypt_html: { } + letsencrypt_acme: { } + nginx_proxy_vhosts: { } diff --git a/docker-compose.sample.yml b/docker-compose.sample.yml index 0661c799e..64878808d 100644 --- a/docker-compose.sample.yml +++ b/docker-compose.sample.yml @@ -10,66 +10,34 @@ # with any changes you need to make. # -services : - nginx_proxy : - container_name : nginx_proxy - image : "ghcr.io/azuracast/nginx_proxy:${AZURACAST_VERSION:-latest}" - ports : - - '${AZURACAST_HTTP_PORT:-80}:80' - - '${AZURACAST_HTTPS_PORT:-443}:443' - volumes : - - letsencrypt:/etc/nginx/certs - - nginx_proxy_vhosts:/etc/nginx/vhost.d - - letsencrypt_html:/usr/share/nginx/html - - /var/run/docker.sock:/tmp/docker.sock:ro - environment : - NGINX_TIMEOUT : ${NGINX_TIMEOUT:-1800} - DEFAULT_HOST : ${LETSENCRYPT_HOST:-azuracast.local} - networks : - - frontend - depends_on : - - web - restart : unless-stopped - - nginx_proxy_letsencrypt : - container_name : nginx_proxy_letsencrypt - image : "ghcr.io/azuracast/nginx_proxy_letsencrypt:${AZURACAST_VERSION:-latest}" - volumes_from : - - nginx_proxy - volumes : - - letsencrypt_acme:/etc/acme.sh - - /var/run/docker.sock:/var/run/docker.sock:ro - environment : - DEFAULT_EMAIL: ${LETSENCRYPT_EMAIL:-} - networks : - - frontend - restart : unless-stopped - - web : - container_name : azuracast_web - image : "ghcr.io/azuracast/web:${AZURACAST_VERSION:-latest}" +services: + web: + container_name: azuracast_web + image: "ghcr.io/azuracast/web:${AZURACAST_VERSION:-latest}" # Want to customize the HTTP/S ports? Follow the instructions here: # https://docs.azuracast.com/en/administration/docker#using-non-standard-ports - ports : + ports: + - '${AZURACAST_HTTP_PORT:-80}:80' + - '${AZURACAST_HTTPS_PORT:-443}:443' - '${AZURACAST_SFTP_PORT:-2022}:2022' - depends_on : + depends_on: - mariadb - stations - redis - env_file : azuracast.env - environment : + env_file: azuracast.env + environment: LANG: ${LANG:-en_US.UTF-8} - AZURACAST_DC_REVISION: 12 + AZURACAST_DC_REVISION: 13 AZURACAST_VERSION: ${AZURACAST_VERSION:-latest} AZURACAST_SFTP_PORT: ${AZURACAST_SFTP_PORT:-2022} - VIRTUAL_HOST: ${LETSENCRYPT_HOST:-azuracast.local} NGINX_TIMEOUT: ${NGINX_TIMEOUT:-1800} LETSENCRYPT_HOST: ${LETSENCRYPT_HOST:-} LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL:-} PUID: ${AZURACAST_PUID:-1000} PGID: ${AZURACAST_PGID:-1000} - volumes : - - letsencrypt:/etc/nginx/certs:ro + volumes: + - letsencrypt:/etc/nginx/certs + - letsencrypt_acme:/etc/acme.sh - www_vendor:/var/azuracast/www/vendor - www_uploads:/var/azuracast/uploads - tmp_data:/var/azuracast/www_tmp @@ -78,49 +46,49 @@ services : - geolite_install:/var/azuracast/geoip - sftpgo_data:/var/azuracast/sftpgo/persist - backups:/var/azuracast/backups - networks : + networks: - frontend - backend - restart : unless-stopped - ulimits : &default-ulimits - nofile : - soft : 65536 - hard : 65536 - logging : &default-logging - options : - max-size : "1m" - max-file : "5" + restart: unless-stopped + ulimits: &default-ulimits + nofile: + soft: 65536 + hard: 65536 + logging: &default-logging + options: + max-size: "1m" + max-file: "5" - mariadb : - container_name : azuracast_mariadb - image : "ghcr.io/azuracast/db:${AZURACAST_VERSION:-latest}" - volumes : + mariadb: + container_name: azuracast_mariadb + image: "ghcr.io/azuracast/db:${AZURACAST_VERSION:-latest}" + volumes: - db_data:/var/lib/mysql - env_file : azuracast.env - networks : + env_file: azuracast.env + networks: - backend - restart : unless-stopped - logging : *default-logging + restart: unless-stopped + logging: *default-logging - redis : - container_name : azuracast_redis - image : "ghcr.io/azuracast/redis:${AZURACAST_VERSION:-latest}" - sysctls : - net.core.somaxconn : 1024 - volumes : + redis: + container_name: azuracast_redis + image: "ghcr.io/azuracast/redis:${AZURACAST_VERSION:-latest}" + sysctls: + net.core.somaxconn: 1024 + volumes: - redis_data:/data - networks : + networks: - backend - restart : unless-stopped - logging : *default-logging + restart: unless-stopped + logging: *default-logging - stations : - container_name : azuracast_stations - image : "ghcr.io/azuracast/radio:${AZURACAST_VERSION:-latest}" - environment : - PUID : ${AZURACAST_PUID:-1000} - PGID : ${AZURACAST_PGID:-1000} - ports : + stations: + container_name: azuracast_stations + image: "ghcr.io/azuracast/radio:${AZURACAST_VERSION:-latest}" + environment: + PUID: ${AZURACAST_PUID:-1000} + PGID: ${AZURACAST_PGID:-1000} + ports: # This default mapping is the outgoing and incoming ports for the first 50 stations. # You can override this port mapping in your own docker-compose.override.yml file. # For instructions, see: @@ -272,37 +240,35 @@ services : - '8490:8490' - '8495:8495' - '8496:8496' - volumes : + volumes: - station_data:/var/azuracast/stations - shoutcast2_install:/var/azuracast/servers/shoutcast2 - letsencrypt:/etc/nginx/certs - tmp_data:/var/azuracast/www_tmp - networks : + networks: - frontend - backend - init : true - restart : unless-stopped - ulimits : *default-ulimits - logging : *default-logging + init: true + restart: unless-stopped + ulimits: *default-ulimits + logging: *default-logging -networks : - frontend : - driver : bridge - backend : - driver : bridge +networks: + frontend: + driver: bridge + backend: + driver: bridge -volumes : - nginx_proxy_vhosts : { } - db_data : { } - letsencrypt : { } - letsencrypt_html : { } - letsencrypt_acme : { } - shoutcast2_install : { } - geolite_install : { } - sftpgo_data : { } - station_data : { } - www_vendor : { } - www_uploads : { } - tmp_data : { } - redis_data : { } - backups : { } +volumes: + db_data: { } + letsencrypt: { } + letsencrypt_acme: { } + shoutcast2_install: { } + geolite_install: { } + sftpgo_data: { } + station_data: { } + www_vendor: { } + www_uploads: { } + tmp_data: { } + redis_data: { } + backups: { } diff --git a/docker.sh b/docker.sh index 45889a328..9a3c29f5e 100755 --- a/docker.sh +++ b/docker.sh @@ -444,8 +444,6 @@ install-dev() { if [[ ! -d ../docker-azuracast-nginx-proxy ]]; then if ask "Clone related repositories?" Y; then - git clone https://github.com/AzuraCast/docker-azuracast-nginx-proxy.git ../docker-azuracast-nginx-proxy - git clone https://github.com/AzuraCast/docker-azuracast-nginx-proxy-letsencrypt.git ../docker-azuracast-nginx-proxy-letsencrypt git clone https://github.com/AzuraCast/docker-azuracast-db.git ../docker-azuracast-db git clone https://github.com/AzuraCast/docker-azuracast-redis.git ../docker-azuracast-redis git clone https://github.com/AzuraCast/docker-azuracast-radio.git ../docker-azuracast-radio diff --git a/src/Console/Command/Internal/OnSslRenewal.php b/src/Console/Command/Internal/OnSslRenewal.php new file mode 100644 index 000000000..212674bc1 --- /dev/null +++ b/src/Console/Command/Internal/OnSslRenewal.php @@ -0,0 +1,46 @@ +em->createQuery( + <<<'DQL' + SELECT s FROM App\Entity\Station s + DQL + )->toIterable(); + + foreach ($stations as $station) { + /** @var Entity\Station $station */ + $frontend = $this->adapters->getFrontendAdapter($station); + if ($frontend->supportsReload()) { + $frontend->reload($station); + } + } + + return 0; + } +} diff --git a/src/Radio/CertificateLocator.php b/src/Radio/CertificateLocator.php index e1290968a..e8c6b0ffa 100644 --- a/src/Radio/CertificateLocator.php +++ b/src/Radio/CertificateLocator.php @@ -12,18 +12,19 @@ class CertificateLocator { $environment = Environment::getInstance(); - if (!empty($_ENV['VIRTUAL_HOST']) && $environment->isDockerRevisionAtLeast(10)) { - $vhost = $_ENV['VIRTUAL_HOST']; - + if ($environment->isDockerRevisionAtLeast(10)) { // Check environment variable for a virtual host. $certBase = '/etc/nginx/certs'; if (is_dir($certBase)) { - $domainKey = $certBase . '/' . $vhost . '.key'; - $domainCert = $certBase . '/' . $vhost . '.crt'; + if (!empty($_ENV['VIRTUAL_HOST'])) { + $vhost = $_ENV['VIRTUAL_HOST']; + $domainKey = $certBase . '/' . $vhost . '.key'; + $domainCert = $certBase . '/' . $vhost . '.crt'; - if (file_exists($domainKey) && file_exists($domainCert)) { - return new Certificate($domainKey, $domainCert); + if (file_exists($domainKey) && file_exists($domainCert)) { + return new Certificate($domainKey, $domainCert); + } } $defaultKey = $certBase . '/default.key'; diff --git a/src/Sync/Task/ReloadFrontendAfterSslChangeTask.php b/src/Sync/Task/ReloadFrontendAfterSslChangeTask.php deleted file mode 100644 index 11b653498..000000000 --- a/src/Sync/Task/ReloadFrontendAfterSslChangeTask.php +++ /dev/null @@ -1,61 +0,0 @@ -getCertPath(), - $certs->getKeyPath(), - ]; - - $certsUpdated = false; - foreach ($pathsToCheck as $path) { - if (file_exists($path) && filemtime($path) > $threshold) { - $certsUpdated = true; - break; - } - } - - if ($certsUpdated) { - $this->logger->info('SSL certificates have updated; hot-reloading stations that support it.'); - - foreach ($this->iterateStations() as $station) { - $frontend = $this->adapters->getFrontendAdapter($station); - if ($frontend->supportsReload()) { - $frontend->reload($station); - } - } - } else { - $this->logger->info('SSL certificates have not updated.'); - } - } -} diff --git a/util/docker/web/nginx/azuracast.conf.tmpl b/util/docker/web/nginx/azuracast.conf.tmpl index 97e5b2bcc..9ac7178a6 100644 --- a/util/docker/web/nginx/azuracast.conf.tmpl +++ b/util/docker/web/nginx/azuracast.conf.tmpl @@ -66,6 +66,18 @@ server { server { listen 80; + listen 443 default_server http2 ssl; + + ssl_certificate /etc/nginx/certs/default.crt; + ssl_certificate_key /etc/nginx/certs/default.key; + + ssl_protocols TLSv1.3 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ecdh_curve secp521r1:secp384r1; + ssl_ciphers EECDH+AESGCM:EECDH+AES256; + + ssl_session_cache shared:TLS:2m; + ssl_buffer_size 4k; root /var/azuracast/www/web; index index.php; @@ -76,6 +88,12 @@ server { add_header X-Content-Type-Options nosniff; add_header Referrer-Policy no-referrer-when-downgrade; + # LetsEncrypt handling + location /.well-known/acme-challenge/ { + root /usr/share/nginx/html; + try_files $uri =404; + } + # Serve a static version of the nowplaying data for non-PHP-blocking delivery. location /api/nowplaying_static { expires 10s; diff --git a/util/docker/web/runit/acme/run b/util/docker/web/runit/acme/run new file mode 100644 index 000000000..0f1b10d8b --- /dev/null +++ b/util/docker/web/runit/acme/run @@ -0,0 +1,190 @@ +#!/bin/bash + +# Acme loading script +# Uses code from: +# https://github.com/nginx-proxy/acme-companion/blob/main/app/letsencrypt_service + +# We set a "LOG_LEVEL" that is incompatible with acme.sh. Overwrite it. +export LOG_LEVEL=1 +export DEBUG=1 + +shopt -s expand_aliases +. /usr/local/acme.sh/acme.sh.env + +# Convert argument to lowercase (bash 4 only) +function lc() { + echo "${@,,}" +} + +CERTS_UPDATE_INTERVAL="${CERTS_UPDATE_INTERVAL:-3600}" +ACME_CA_URI="${ACME_CA_URI:-"https://acme-v02.api.letsencrypt.org/directory"}" +ACME_CA_TEST_URI="https://acme-staging-v02.api.letsencrypt.org/directory" +DEFAULT_KEY_SIZE="${DEFAULT_KEY_SIZE:-4096}" +RENEW_PRIVATE_KEYS="$(lc "${RENEW_PRIVATE_KEYS:-true}")" + +# Backward compatibility environment variable +REUSE_PRIVATE_KEYS="$(lc "${REUSE_PRIVATE_KEYS:-false}")" + +function update_cert { + local hosts_array + IFS=',' read -ra hosts_array <<< "$LETSENCRYPT_HOST" + + local base_domain="${hosts_array[0]}" + + # Base CLI parameters array, used for both --register-account and --issue + local -a params_base_arr + + params_base_arr+=(--log /dev/null) + [[ "$DEBUG" == 1 ]] && params_base_arr+=(--debug 2) + + # Alternative trusted root CA path, used for test with Pebble + if [[ -n "${CA_BUNDLE// }" ]]; then + if [[ -f "$CA_BUNDLE" ]]; then + params_base_arr+=(--ca-bundle "$CA_BUNDLE") + [[ "$DEBUG" == 1 ]] && echo "Debug: acme.sh will use $CA_BUNDLE as trusted root CA." + else + echo "Warning: the path to the alternate CA bundle ($CA_BUNDLE) is not valid, using default Alpine trust store." + fi + fi + + # CLI parameters array used for --register-account + local -a params_register_arr + + # CLI parameters array used for --issue + local -a params_issue_arr + params_issue_arr+=(--webroot /usr/share/nginx/html) + + local -n cert_keysize="LETSENCRYPT_KEYSIZE" + if [[ -z "$cert_keysize" ]] || \ + [[ ! "$cert_keysize" =~ ^(2048|3072|4096|ec-256|ec-384)$ ]]; then + cert_keysize=$DEFAULT_KEY_SIZE + fi + params_issue_arr+=(--keylength "$cert_keysize") + + # OCSP-Must-Staple extension + local -n ocsp="ACME_OCSP" + if [[ $(lc "$ocsp") == true ]]; then + params_issue_arr+=(--ocsp-must-staple) + fi + + local -n accountemail="LETSENCRYPT_EMAIL" + local config_home + # If we don't have a LETSENCRYPT_EMAIL from the proxied container + # and DEFAULT_EMAIL is set to a non empty value, use the latter. + if [[ -z "$accountemail" ]]; then + if [[ -n "${DEFAULT_EMAIL// }" ]]; then + accountemail="$DEFAULT_EMAIL" + else + unset accountemail + fi + fi + + if [[ -n "${accountemail// }" ]]; then + # If we got an email, use it with the corresponding config home + config_home="/etc/acme.sh/$accountemail" + else + # If we did not get any email at all, use the default (empty mail) config + config_home="/etc/acme.sh/default" + fi + + local -n acme_ca_uri="ACME_CA_URI" + if [[ -z "$acme_ca_uri" ]]; then + # Use default or user provided ACME end point + acme_ca_uri="$ACME_CA_URI" + fi + + # LETSENCRYPT_TEST overrides LETSENCRYPT_ACME_CA_URI + local -n test_certificate="LETSENCRYPT_TEST" + if [[ $(lc "$test_certificate") == true ]]; then + # Use Let's Encrypt ACME V2 staging end point + acme_ca_uri="$ACME_CA_TEST_URI" + fi + + # Set relevant --server parameter and ca folder name + params_base_arr+=(--server "$acme_ca_uri") + local ca_dir="${acme_ca_uri##*://}" \ + && ca_dir="${ca_dir%%:*}" + + local certificate_dir + # If we're going to use one of LE stating endpoints ... + if [[ "$acme_ca_uri" =~ ^https://acme-staging.* ]]; then + # Unset accountemail + # force config dir to 'staging' + unset accountemail + config_home="/etc/acme.sh/staging" + # Prefix test certificate directory with _test_ + certificate_dir="/etc/nginx/certs/_test_" + else + certificate_dir="/etc/nginx/certs" + fi + + params_issue_arr+=( \ + --cert-file "${certificate_dir}/default.crt" \ + --key-file "${certificate_dir}/default.key" \ + --reloadcmd "/usr/local/bin/on_ssl_renewal" \ + ) + + [[ ! -d "$config_home" ]] && mkdir -p "$config_home" + + params_base_arr+=(--config-home "$config_home") + local account_file="${config_home}/ca/${ca_dir}/account.json" + + if [[ -n "${accountemail// }" ]]; then + # We're not using Zero SSL, register the ACME account using the provided email. + params_register_arr+=(--accountemail "$accountemail") + fi + + # Account registration and update if required + if [[ ! -f "$account_file" ]]; then + params_register_arr=("${params_base_arr[@]}" "${params_register_arr[@]}") + [[ "$DEBUG" == 1 ]] && echo "Calling acme.sh --register-account with the following parameters : ${params_register_arr[*]}" + acme.sh --register-account "${params_register_arr[@]}" + fi + + if [[ -n "${accountemail// }" ]] && ! grep -q "mailto:$accountemail" "$account_file"; then + local -a params_update_arr=("${params_base_arr[@]}" --accountemail "$accountemail") + [[ "$DEBUG" == 1 ]] && echo "Calling acme.sh --update-account with the following parameters : ${params_update_arr[*]}" + acme.sh --update-account "${params_update_arr[@]}" + fi + + # If we still don't have an account.json file by this point, we've got an issue + if [[ ! -f "$account_file" ]]; then + echo "Error: no ACME account was found or registered for $accountemail and $acme_ca_uri, certificate creation aborted." + return 1 + fi + + local -n acme_preferred_chain="ACME_PREFERRED_CHAIN" + if [[ -n "${acme_preferred_chain}" ]]; then + # Using amce.sh --preferred-chain to select alternate chain. + params_issue_arr+=(--preferred-chain "$acme_preferred_chain") + fi + if [[ "$RENEW_PRIVATE_KEYS" != 'false' && "$REUSE_PRIVATE_KEYS" != 'true' ]]; then + params_issue_arr+=(--always-force-new-domain-key) + fi + + [[ "${2:-}" == "--force-renew" ]] && params_issue_arr+=(--force) + + # Create directory for the first domain + mkdir -p "$certificate_dir" + + for domain in "${hosts_array[@]}"; do + # Add all the domains to certificate + params_issue_arr+=(--domain "$domain") + done + + params_issue_arr=("${params_base_arr[@]}" "${params_issue_arr[@]}") + [[ "$DEBUG" == 1 ]] && echo "Calling acme.sh --issue with the following parameters : ${params_issue_arr[*]}" + echo "Creating/renewal $base_domain certificates... (${hosts_array[*]})" + acme.sh --issue "${params_issue_arr[@]}" +} + +if [ ! -z "$VIRTUAL_HOST" ]; then + echo "Multi-site configuration detected; skipping local ACME setup." +elif [ ! -z "$LETSENCRYPT_HOST" -a "$LETSENCRYPT_HOST" != " " ]; then + update_cert "$@" +fi + +# Wait some amount of time +echo "Sleep for ${CERTS_UPDATE_INTERVAL}s" +sleep $CERTS_UPDATE_INTERVAL +exit diff --git a/util/docker/web/runit/beanstalkd/run b/util/docker/web/runit/beanstalkd/run index 99f41e57c..91ca77b06 100644 --- a/util/docker/web/runit/beanstalkd/run +++ b/util/docker/web/runit/beanstalkd/run @@ -2,4 +2,4 @@ echo 'Spinning up Beanstalkd process...' -setuser azuracast beanstalkd -p 11300 -z 262140 +exec sudo -E -u azuracast beanstalkd -p 11300 -z 262140 diff --git a/util/docker/web/runit/nginx/run b/util/docker/web/runit/nginx/run index 88c3a13b0..bbb4eea38 100644 --- a/util/docker/web/runit/nginx/run +++ b/util/docker/web/runit/nginx/run @@ -1,3 +1,3 @@ #!/bin/bash -nginx -g "daemon off;" \ No newline at end of file +exec nginx -g "daemon off;" diff --git a/util/docker/web/runit/php-fpm/run b/util/docker/web/runit/php-fpm/run index 3a73c48f5..a087e05f7 100644 --- a/util/docker/web/runit/php-fpm/run +++ b/util/docker/web/runit/php-fpm/run @@ -2,4 +2,4 @@ source /etc/php/.version -/usr/sbin/php-fpm${PHP_VERSION} -F --fpm-config /etc/php/${PHP_VERSION}/fpm/php-fpm.conf -c /etc/php/${PHP_VERSION}/fpm/ +exec /usr/sbin/php-fpm${PHP_VERSION} -F --fpm-config /etc/php/${PHP_VERSION}/fpm/php-fpm.conf -c /etc/php/${PHP_VERSION}/fpm/ diff --git a/util/docker/web/runit/php-nowplaying/run b/util/docker/web/runit/php-nowplaying/run index cdaa744c9..1797f18c0 100644 --- a/util/docker/web/runit/php-nowplaying/run +++ b/util/docker/web/runit/php-nowplaying/run @@ -1,3 +1,3 @@ #!/bin/bash -sudo -E -u azuracast php /var/azuracast/www/bin/console azuracast:sync:nowplaying +exec sudo -E -u azuracast php /var/azuracast/www/bin/console azuracast:sync:nowplaying diff --git a/util/docker/web/runit/php-worker/run b/util/docker/web/runit/php-worker/run index cf9a0b635..9cb618615 100644 --- a/util/docker/web/runit/php-worker/run +++ b/util/docker/web/runit/php-worker/run @@ -1,3 +1,3 @@ #!/bin/bash -sudo -E -u azuracast php /var/azuracast/www/bin/console queue:process --worker-name=app_worker_0 +exec sudo -E -u azuracast php /var/azuracast/www/bin/console queue:process --worker-name=app_worker_0 diff --git a/util/docker/web/runit/sftpgo/run b/util/docker/web/runit/sftpgo/run index c277f1229..985f63a5e 100644 --- a/util/docker/web/runit/sftpgo/run +++ b/util/docker/web/runit/sftpgo/run @@ -4,4 +4,4 @@ echo 'Spinning up SFTP process...' cd /var/azuracast/sftpgo -sudo -E -u azuracast sftpgo --config-dir=/var/azuracast/sftpgo serve -l "" > /proc/1/fd/1 2> /proc/1/fd/2 \ No newline at end of file +exec sudo -E -u azuracast sftpgo --config-dir=/var/azuracast/sftpgo serve -l "" > /proc/1/fd/1 2> /proc/1/fd/2 diff --git a/util/docker/web/scripts/azuracast_cli b/util/docker/web/scripts/azuracast_cli index 07d2cd9e0..7f6bc7c0c 100644 --- a/util/docker/web/scripts/azuracast_cli +++ b/util/docker/web/scripts/azuracast_cli @@ -2,9 +2,8 @@ if [ `whoami` != 'azuracast' ]; then echo 'This script must be run as the "azuracast" user. Rerunning...' - setuser azuracast azuracast_cli "$@" - exit $? + exec sudo -E -u azuracast azuracast azuracast_cli "$@" fi cd /var/azuracast/www -php bin/console "$@" +exec php bin/console "$@" diff --git a/util/docker/web/scripts/azuracast_install b/util/docker/web/scripts/azuracast_install index e2e3158f2..9319f9c10 100644 --- a/util/docker/web/scripts/azuracast_install +++ b/util/docker/web/scripts/azuracast_install @@ -32,4 +32,4 @@ else composer install fi -azuracast_cli azuracast:setup "$@" +exec azuracast_cli azuracast:setup "$@" diff --git a/util/docker/web/scripts/azuracast_restore b/util/docker/web/scripts/azuracast_restore index ed7feed61..87b11819f 100644 --- a/util/docker/web/scripts/azuracast_restore +++ b/util/docker/web/scripts/azuracast_restore @@ -26,4 +26,4 @@ else composer install fi -azuracast_cli azuracast:restore "$@" +exec azuracast_cli azuracast:restore "$@" diff --git a/util/docker/web/scripts/azuracast_sftp_auth b/util/docker/web/scripts/azuracast_sftp_auth index 5e1ff7fd5..548abcad6 100644 --- a/util/docker/web/scripts/azuracast_sftp_auth +++ b/util/docker/web/scripts/azuracast_sftp_auth @@ -2,4 +2,4 @@ source /etc/container_environment.sh -azuracast_cli azuracast:internal:sftp-auth "$@" \ No newline at end of file +exec azuracast_cli azuracast:internal:sftp-auth "$@" diff --git a/util/docker/web/scripts/azuracast_sftp_event b/util/docker/web/scripts/azuracast_sftp_event index 783fdddff..570b2bdae 100644 --- a/util/docker/web/scripts/azuracast_sftp_event +++ b/util/docker/web/scripts/azuracast_sftp_event @@ -2,4 +2,4 @@ source /etc/container_environment.sh -azuracast_cli azuracast:internal:sftp-event "$@" +exec azuracast_cli azuracast:internal:sftp-event "$@" diff --git a/util/docker/web/scripts/azuracast_update b/util/docker/web/scripts/azuracast_update index 7e6560ec2..df01ddbdb 100644 --- a/util/docker/web/scripts/azuracast_update +++ b/util/docker/web/scripts/azuracast_update @@ -1,3 +1,3 @@ #!/usr/bin/env bash -azuracast_install --update "$@" +exec azuracast_install --update "$@" diff --git a/util/docker/web/scripts/cron_task b/util/docker/web/scripts/cron_task index 75da12636..0c96cc75c 100644 --- a/util/docker/web/scripts/cron_task +++ b/util/docker/web/scripts/cron_task @@ -2,4 +2,4 @@ source /etc/container_environment.sh -setuser azuracast "$@" >/proc/1/fd/1 2>/proc/1/fd/2 +exec sudo -E -u azuracast "$@" >/proc/1/fd/1 2>/proc/1/fd/2 diff --git a/util/docker/web/scripts/docker_installer b/util/docker/web/scripts/docker_installer index bf3ab776b..61fd03e80 100644 --- a/util/docker/web/scripts/docker_installer +++ b/util/docker/web/scripts/docker_installer @@ -1,4 +1,4 @@ #!/usr/bin/env bash cd /var/azuracast/www -php bin/installer "$@" +exec php bin/installer "$@" diff --git a/util/docker/web/scripts/on_ssl_renewal b/util/docker/web/scripts/on_ssl_renewal new file mode 100644 index 000000000..1d7eeef3f --- /dev/null +++ b/util/docker/web/scripts/on_ssl_renewal @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +sv restart nginx + +azuracast_cli azuracast:internal:on-ssl-renewal diff --git a/util/docker/web/scripts/setuser b/util/docker/web/scripts/setuser deleted file mode 100644 index f7897e485..000000000 --- a/util/docker/web/scripts/setuser +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/python3 - -''' -Copyright (c) 2013-2015 Phusion Holding B.V. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -''' - -import sys -import os -import pwd - - -def abort(message): - sys.stderr.write("setuser: %s\n" % message) - sys.exit(1) - - -def main(): - ''' - A simple alternative to sudo that executes a command as a user by setting - the user ID and user parameters to those described by the system and then - using execvp(3) to execute the command without the necessity of a TTY - ''' - - username = sys.argv[1] - try: - user = pwd.getpwnam(username) - except KeyError: - abort("user %s not found" % username) - os.initgroups(username, user.pw_gid) - os.setgid(user.pw_gid) - os.setuid(user.pw_uid) - os.environ['USER'] = username - os.environ['HOME'] = user.pw_dir - os.environ['UID'] = str(user.pw_uid) - try: - os.execvp(sys.argv[2], sys.argv[2:]) - except OSError as e: - abort("cannot execute %s: %s" % (sys.argv[2], str(e))) - -if __name__ == '__main__': - - if len(sys.argv) < 3: - sys.stderr.write("Usage: /sbin/setuser USERNAME COMMAND [args..]\n") - sys.exit(1) - - main() diff --git a/util/docker/web/scripts/temp_cleanup b/util/docker/web/scripts/temp_cleanup index b4e5038f7..5c1bd3a98 100644 --- a/util/docker/web/scripts/temp_cleanup +++ b/util/docker/web/scripts/temp_cleanup @@ -1,6 +1,6 @@ #!/usr/bin/env bash -/usr/sbin/tmpreaper 12h --protect '.tmpreaper' --verbose \ +exec /usr/sbin/tmpreaper 12h --protect '.tmpreaper' --verbose \ /tmp/azuracast_nginx_client \ /tmp/azuracast_fastcgi_temp \ - > /proc/1/fd/1 2> /proc/1/fd/2 \ No newline at end of file + > /proc/1/fd/1 2> /proc/1/fd/2 diff --git a/util/docker/web/setup/acme_sh.sh b/util/docker/web/setup/acme_sh.sh new file mode 100644 index 000000000..e25d0a853 --- /dev/null +++ b/util/docker/web/setup/acme_sh.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +# Get acme.sh ACME client source +mkdir /src +git -C /src clone https://github.com/acmesh-official/acme.sh.git +cd /src/acme.sh + +# Install acme.sh in /app +./acme.sh --install \ + --nocron \ + --auto-upgrade 0 \ + --home /usr/local/acme.sh \ + --config-home /etc/acme.sh/default + +# Make house cleaning +cd / +rm -rf /src diff --git a/util/docker/web/startup_scripts/01_self_signed_ssl.sh b/util/docker/web/startup_scripts/01_self_signed_ssl.sh new file mode 100644 index 000000000..333b4674f --- /dev/null +++ b/util/docker/web/startup_scripts/01_self_signed_ssl.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +# Generate a self-signed certificate if one doesn't exist in the certs path. +if [ ! -f /etc/nginx/certs/default.crt ]; then + echo "Generating self-signed certificate..." + + openssl req -new -nodes -x509 -subj "/C=US/ST=Texas/L=Austin/O=IT/CN=localhost" \ + -days 365 -extensions v3_ca \ + -keyout /etc/nginx/certs/default.key \ + -out /etc/nginx/certs/default.crt +fi