From 153f7a8ec72a58758c92c09582c1aaa6e3d649d4 Mon Sep 17 00:00:00 2001 From: "Buster \"Silver Eagle\" Neece" Date: Mon, 20 Jan 2020 22:24:45 -0600 Subject: [PATCH] Add web container Docker code to this repo; tie image to code version. --- .dockerignore | 17 + Dockerfile | 84 ++++ docker-compose.dev.yml | 2 +- util/docker/web/add_user.sh | 18 + util/docker/web/buildconfig | 4 + util/docker/web/cleanup.sh | 9 + util/docker/web/cron/azuracast | 9 + util/docker/web/letsencrypt/01-reload-nginx | 4 + util/docker/web/nginx/azuracast.conf | 155 +++++++ util/docker/web/nginx/nginx.conf | 40 ++ util/docker/web/php/php.ini.tmpl | 15 + util/docker/web/php/www.conf.tmpl | 24 + util/docker/web/prepare.sh | 70 +++ util/docker/web/runit/cron/run | 8 + util/docker/web/runit/nginx/run | 3 + util/docker/web/runit/php-fpm/run | 3 + util/docker/web/runit/php-worker/run | 3 + util/docker/web/runit/sftpgo/run | 7 + util/docker/web/scripts/azuracast_cli | 10 + util/docker/web/scripts/azuracast_install | 72 +++ .../web/scripts/azuracast_migrate_stations | 3 + util/docker/web/scripts/azuracast_restore | 54 +++ util/docker/web/scripts/azuracast_sftp_auth | 5 + util/docker/web/scripts/azuracast_sftp_upload | 5 + util/docker/web/scripts/azuracast_update | 3 + util/docker/web/scripts/cron_task | 5 + util/docker/web/scripts/letsencrypt_connect | 44 ++ util/docker/web/scripts/letsencrypt_renew | 9 + util/docker/web/scripts/letsencrypt_uninstall | 25 ++ util/docker/web/scripts/my_init | 420 ++++++++++++++++++ util/docker/web/scripts/temp_cleanup | 6 + util/docker/web/setup.sh | 26 ++ util/docker/web/setup/certbot.sh | 26 ++ util/docker/web/setup/cron.sh | 24 + util/docker/web/setup/dockerize.sh | 13 + util/docker/web/setup/influxdb.sh | 11 + util/docker/web/setup/nginx.sh | 19 + util/docker/web/setup/php.sh | 22 + util/docker/web/setup/sftpgo.sh | 11 + util/docker/web/setup/tmpreaper.sh | 6 + util/docker/web/sftpgo/sftpgo.json | 33 ++ util/docker/web/startup_scripts/php.sh | 4 + .../docker/web/startup_scripts/php_workers.sh | 12 + .../web/startup_scripts/sftpgo_private_key.sh | 7 + 44 files changed, 1349 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 util/docker/web/add_user.sh create mode 100644 util/docker/web/buildconfig create mode 100644 util/docker/web/cleanup.sh create mode 100644 util/docker/web/cron/azuracast create mode 100644 util/docker/web/letsencrypt/01-reload-nginx create mode 100644 util/docker/web/nginx/azuracast.conf create mode 100644 util/docker/web/nginx/nginx.conf create mode 100644 util/docker/web/php/php.ini.tmpl create mode 100644 util/docker/web/php/www.conf.tmpl create mode 100644 util/docker/web/prepare.sh create mode 100644 util/docker/web/runit/cron/run create mode 100644 util/docker/web/runit/nginx/run create mode 100644 util/docker/web/runit/php-fpm/run create mode 100644 util/docker/web/runit/php-worker/run create mode 100644 util/docker/web/runit/sftpgo/run create mode 100644 util/docker/web/scripts/azuracast_cli create mode 100644 util/docker/web/scripts/azuracast_install create mode 100644 util/docker/web/scripts/azuracast_migrate_stations create mode 100644 util/docker/web/scripts/azuracast_restore create mode 100644 util/docker/web/scripts/azuracast_sftp_auth create mode 100644 util/docker/web/scripts/azuracast_sftp_upload create mode 100644 util/docker/web/scripts/azuracast_update create mode 100644 util/docker/web/scripts/cron_task create mode 100644 util/docker/web/scripts/letsencrypt_connect create mode 100644 util/docker/web/scripts/letsencrypt_renew create mode 100644 util/docker/web/scripts/letsencrypt_uninstall create mode 100644 util/docker/web/scripts/my_init create mode 100644 util/docker/web/scripts/temp_cleanup create mode 100644 util/docker/web/setup.sh create mode 100644 util/docker/web/setup/certbot.sh create mode 100644 util/docker/web/setup/cron.sh create mode 100644 util/docker/web/setup/dockerize.sh create mode 100644 util/docker/web/setup/influxdb.sh create mode 100644 util/docker/web/setup/nginx.sh create mode 100644 util/docker/web/setup/php.sh create mode 100644 util/docker/web/setup/sftpgo.sh create mode 100644 util/docker/web/setup/tmpreaper.sh create mode 100644 util/docker/web/sftpgo/sftpgo.json create mode 100644 util/docker/web/startup_scripts/php.sh create mode 100644 util/docker/web/startup_scripts/php_workers.sh create mode 100644 util/docker/web/startup_scripts/sftpgo_private_key.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..4ddbe2685 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Files in the root directory +/* +/.* + +!/composer.json +!/composer.lock + +# Git/GitHub internal directories +/.git +/.github + +# Non-code folders +/frontend +/resources +!/resources/locale/compiled +/util +!/util/docker/web \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..228375f19 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,84 @@ +# SFTPGo build stage +FROM golang:stretch as sftpgo +LABEL maintainer="nicola.murino@gmail.com" + +RUN go get -d github.com/drakkan/sftpgo +WORKDIR /go/src/github.com/drakkan/sftpgo + +ARG build_commit="8e604f888a3a3c324500ba261cd1789ee8d80f0d" + +# uncomment the next line to get the latest stable version instead of the latest git +# RUN git checkout `git rev-list --tags --max-count=1` +RUN go build -i -ldflags "-s -w -X github.com/drakkan/sftpgo/utils.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/utils.date=`date -u +%FT%TZ`" -o sftpgo + +# +# Main build stage +# +FROM ubuntu:bionic + +ENV TZ="UTC" + +# Run base build process +COPY ./util/docker/web/ /bd_build + +COPY --from=sftpgo /go/src/github.com/drakkan/sftpgo/sftpgo /usr/local/sbin/sftpgo + +RUN chmod a+x /bd_build/*.sh \ + && /bd_build/prepare.sh \ + && /bd_build/add_user.sh \ + && /bd_build/setup.sh \ + && /bd_build/cleanup.sh \ + && rm -rf /bd_build + +# +# START Operations as `azuracast` user +# +USER azuracast + +WORKDIR /var/azuracast/www + +# Add code +COPY --chown=azuracast:azuracast ./composer.json ./composer.lock ./ +RUN composer install \ + --ignore-platform-reqs \ + --no-ansi \ + --no-autoloader \ + --no-interaction \ + --no-scripts + +# We need to copy our whole application so that we can generate the autoload file inside the vendor folder. +COPY --chown=azuracast:azuracast . . + +RUN composer dump-autoload --optimize --classmap-authoritative \ + && touch /var/azuracast/.docker + +VOLUME ["/var/azuracast/www", "/var/azuracast/backups", "/etc/letsencrypt", "/var/azuracast/sftpgo/persist"] + +# +# END Operations as `azuracast` user +# +USER root + +EXPOSE 80 443 2022 + +# Nginx Proxy environment variables. +ENV VIRTUAL_HOST="azuracast.local" \ + HTTPS_METHOD="noredirect" + +# Sensible default environment variables. +ENV APPLICATION_ENV="production" \ + MYSQL_HOST="mariadb" \ + MYSQL_PORT=3306 \ + MYSQL_USER="azuracast" \ + MYSQL_PASSWORD="azur4c457" \ + MYSQL_DATABASE="azuracast" \ + PREFER_RELEASE_BUILDS="false" \ + COMPOSER_PLUGIN_MODE="false" \ + ADDITIONAL_MEDIA_SYNC_WORKER_COUNT=0 + +# Entrypoint and default command +ENTRYPOINT ["dockerize",\ + "-wait","tcp://mariadb:3306",\ + "-wait","tcp://influxdb:8086",\ + "-timeout","40s"] +CMD ["/usr/local/bin/my_init"] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 96a9ba98c..564e708c3 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -18,7 +18,7 @@ services: web: image: azuracast/azuracast_web_v2:latest build: - context: ../docker-azuracast-web-v2 + context: . depends_on: - mariadb - influxdb diff --git a/util/docker/web/add_user.sh b/util/docker/web/add_user.sh new file mode 100644 index 000000000..a8a2238bd --- /dev/null +++ b/util/docker/web/add_user.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +$minimal_apt_get_install sudo + +adduser --home /var/azuracast --disabled-password --gecos "" azuracast + +usermod -aG docker_env azuracast +usermod -aG www-data azuracast + +mkdir -p /var/azuracast/www /var/azuracast/backups /var/azuracast/www_tmp /var/azuracast/geoip + +chown -R azuracast:azuracast /var/azuracast +chmod -R 777 /var/azuracast/www_tmp + +echo 'azuracast ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers \ No newline at end of file diff --git a/util/docker/web/buildconfig b/util/docker/web/buildconfig new file mode 100644 index 000000000..da99c4034 --- /dev/null +++ b/util/docker/web/buildconfig @@ -0,0 +1,4 @@ +export LC_ALL=C +export DEBIAN_FRONTEND=noninteractive + +minimal_apt_get_install='apt-get install -y --no-install-recommends' \ No newline at end of file diff --git a/util/docker/web/cleanup.sh b/util/docker/web/cleanup.sh new file mode 100644 index 000000000..f26907f1f --- /dev/null +++ b/util/docker/web/cleanup.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +apt-get clean +rm -rf /var/lib/apt/lists/* + +rm -rf /tmp/tmp* \ No newline at end of file diff --git a/util/docker/web/cron/azuracast b/util/docker/web/cron/azuracast new file mode 100644 index 000000000..6421f9d99 --- /dev/null +++ b/util/docker/web/cron/azuracast @@ -0,0 +1,9 @@ +0 * * * * root /usr/local/bin/cron_task azuracast_cli sync:run long +*/5 * * * * root /usr/local/bin/cron_task azuracast_cli sync:run medium +* * * * * root /usr/local/bin/cron_task azuracast_cli sync:run short +* * * * * root /usr/local/bin/cron_task azuracast_cli sync:run nowplaying +* * * * * root sleep 15; /usr/local/bin/cron_task azuracast_cli sync:run nowplaying +* * * * * root sleep 30; /usr/local/bin/cron_task azuracast_cli sync:run nowplaying +* * * * * root sleep 45; /usr/local/bin/cron_task azuracast_cli sync:run nowplaying +0 0 * * * root /usr/local/bin/cron_task letsencrypt_renew +30 */6 * * * root /usr/local/bin/temp_cleanup diff --git a/util/docker/web/letsencrypt/01-reload-nginx b/util/docker/web/letsencrypt/01-reload-nginx new file mode 100644 index 000000000..489ef0cea --- /dev/null +++ b/util/docker/web/letsencrypt/01-reload-nginx @@ -0,0 +1,4 @@ +#! /bin/bash +set -e + +sudo kill -HUP `sudo cat /var/run/nginx.pid` \ No newline at end of file diff --git a/util/docker/web/nginx/azuracast.conf b/util/docker/web/nginx/azuracast.conf new file mode 100644 index 000000000..d7099b799 --- /dev/null +++ b/util/docker/web/nginx/azuracast.conf @@ -0,0 +1,155 @@ +upstream redis_server { + nchan_redis_server "redis://redis:6379"; +} + +server { + listen 9010; + + location ~ /pub/(\w+)$ { + nchan_publisher; + nchan_redis_pass redis_server; + + nchan_channel_group "azuracast_nowplaying"; + nchan_channel_id $1; + + nchan_message_buffer_length 1; + nchan_message_timeout 16s; + } +} + +server { + listen 80; + listen 443 default_server http2 ssl; + + ssl_certificate /etc/letsencrypt/ssl.crt; + ssl_certificate_key /etc/letsencrypt/ssl.key; + + ssl_protocols TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_dhparam /etc/nginx/dhparam.pem; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; + ssl_ecdh_curve secp521r1:secp384r1:prime256v1; + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + + root /var/azuracast/www/web; + index index.php; + + server_name localhost; + + add_header X-XSS-Protection 1; + add_header X-Content-Type-Options nosniff; + add_header X-Robots-Tag none; + add_header Referrer-Policy no-referrer-when-downgrade; + + # LetsEncrypt handling + location /.well-known/acme-challenge/ { + root /var/www/letsencrypt; + try_files $uri =404; + } + + # Serve a static version of the nowplaying data for non-PHP-blocking delivery. + location /api/nowplaying_static { + expires 10s; + add_header Access-Control-Allow-Origin *; + + alias /var/azuracast/www_tmp/nowplaying; + try_files $uri =404; + } + + # Default clean URL routing + location / { + try_files $uri @clean_url; + } + + location @clean_url { + rewrite ^(.*)$ /index.php last; + } + + # Set up caching for static assets. + location /static { + add_header Access-Control-Allow-Origin *; + } + + location /static/dist { + expires 365d; + } + + location ~ ^/index\.php(/|$) { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + + # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + fastcgi_pass localhost:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + include fastcgi_params; + + fastcgi_read_timeout 1800; + fastcgi_buffering off; + + internal; + } + + # Return 404 for all other php files not matching the front controller + location ~ \.php$ { + return 404; + } + + # Reverse proxy all possible radio listening ports (8000, 8010...8480, 8490) + location ~ ^/radio/(8[0-9][0-9]0)(/?)(.*)$ { + resolver 127.0.0.11; + + proxy_buffering off; + proxy_ignore_client_abort off; + proxy_intercept_errors on; + proxy_next_upstream error timeout invalid_header; + proxy_redirect off; + proxy_connect_timeout 60; + proxy_send_timeout 21600; + proxy_read_timeout 21600; + + proxy_set_header Host localhost:$1; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://stations:$1/$3?$args; + } + + # Reverse proxy the Liquidsoap harbor inputs to allow for streaming. + location ~ ^/radio/(8[0-9][0-9]5)(/?)(.*)$ { + resolver 127.0.0.11; + + proxy_buffering off; + proxy_ignore_client_abort off; + proxy_send_timeout 21600; + proxy_read_timeout 21600; + + proxy_pass http://stations:$1/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } + + # pub/sub endpoints + location ~ /api/live/nowplaying/(\w+)$ { + nchan_access_control_allow_origin "*"; + + nchan_subscriber; + nchan_redis_pass redis_server; + + nchan_channel_group "azuracast_nowplaying"; + nchan_channel_id "$1"; + nchan_channel_id_split_delimiter ","; + + nchan_subscriber_first_message -1; + } + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + location ~ /\.ht { + deny all; + } +} diff --git a/util/docker/web/nginx/nginx.conf b/util/docker/web/nginx/nginx.conf new file mode 100644 index 000000000..099954584 --- /dev/null +++ b/util/docker/web/nginx/nginx.conf @@ -0,0 +1,40 @@ +user azuracast; +worker_processes auto; + +error_log /dev/stderr warn; +pid /var/run/nginx.pid; + +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 4096; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log off; + + server_tokens off; + + sendfile off; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + client_max_body_size 50M; + client_body_temp_path /tmp/azuracast_nginx_client 1 1; + + fastcgi_temp_path /tmp/azuracast_fastcgi_temp 1 1; + + proxy_max_temp_file_size 0; + + include /etc/nginx/conf.d/*.conf; +} \ No newline at end of file diff --git a/util/docker/web/php/php.ini.tmpl b/util/docker/web/php/php.ini.tmpl new file mode 100644 index 000000000..cf1336903 --- /dev/null +++ b/util/docker/web/php/php.ini.tmpl @@ -0,0 +1,15 @@ +# AzuraCast custom PHP specifications + +[php] +post_max_size={{ default .Env.PHP_MAX_FILE_SIZE "50M" }} +upload_max_filesize={{ default .Env.PHP_MAX_FILE_SIZE "50M" }} +memory_limit={{ default .Env.PHP_MEMORY_LIMIT "256M" }} +max_execution_time={{ default .Env.PHP_MAX_EXECUTION_TIME "30" }} + +[opcache] +opcache.enable=1 +opcache.enable_cli=1 + +{{if eq .Env.APPLICATION_ENV "production"}} +opcache.revalidate_freq={{ default .Env.PHP_OPCACHE_REVALIDATE_FREQUENCY "60" }} +{{end}} \ No newline at end of file diff --git a/util/docker/web/php/www.conf.tmpl b/util/docker/web/php/www.conf.tmpl new file mode 100644 index 000000000..78d2eaf3a --- /dev/null +++ b/util/docker/web/php/www.conf.tmpl @@ -0,0 +1,24 @@ +[global] + +error_log = syslog +daemonize = no + +[www] +user = azuracast +group = azuracast + +listen = 0.0.0.0:9000 + +pm = ondemand +pm.max_children = {{ default .Env.PHP_FPM_MAX_CHILDREN "5" }} +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +pm.max_requests = 100 +pm.process_idle_timeout = 60s + +chdir = / + +clear_env=No +catch_workers_output = yes +decorate_workers_output = no \ No newline at end of file diff --git a/util/docker/web/prepare.sh b/util/docker/web/prepare.sh new file mode 100644 index 000000000..47dc6ff72 --- /dev/null +++ b/util/docker/web/prepare.sh @@ -0,0 +1,70 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +## Prevent initramfs updates from trying to run grub and lilo. +## https://journal.paul.querna.org/articles/2013/10/15/docker-ubuntu-on-rackspace/ +## http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=594189 +export INITRD=no +mkdir -p /etc/container_environment +echo -n no > /etc/container_environment/INITRD + +# Add default timezone. +echo "UTC" > /etc/timezone + +# Avoid ERROR: invoke-rc.d: policy-rc.d denied execution of start. +sed -i "s/^exit 101$/exit 0/" /usr/sbin/policy-rc.d + +## Enable Ubuntu Universe, Multiverse, and deb-src for main. +sed -i 's/^#\s*\(deb.*main restricted\)$/\1/g' /etc/apt/sources.list +sed -i 's/^#\s*\(deb.*universe\)$/\1/g' /etc/apt/sources.list +sed -i 's/^#\s*\(deb.*multiverse\)$/\1/g' /etc/apt/sources.list +apt-get update + +## Fix some issues with APT packages. +## See https://github.com/dotcloud/docker/issues/1024 +dpkg-divert --local --rename --add /sbin/initctl +ln -sf /bin/true /sbin/initctl + +## Replace the 'ischroot' tool to make it always return true. +## Prevent initscripts updates from breaking /dev/shm. +## https://journal.paul.querna.org/articles/2013/10/15/docker-ubuntu-on-rackspace/ +## https://bugs.launchpad.net/launchpad/+bug/974584 +dpkg-divert --local --rename --add /usr/bin/ischroot +ln -sf /bin/true /usr/bin/ischroot + +# apt-utils fix for Ubuntu 16.04 +$minimal_apt_get_install apt-utils + +## Install HTTPS support for APT. +$minimal_apt_get_install apt-transport-https ca-certificates + +## Install add-apt-repository +$minimal_apt_get_install software-properties-common + +## Upgrade all packages. +apt-get dist-upgrade -y --no-install-recommends -o Dpkg::Options::="--force-confold" + +## Fix locale. +$minimal_apt_get_install language-pack-en + +locale-gen en_US +update-locale LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 + +echo -n en_US.UTF-8 > /etc/container_environment/LANG +echo -n en_US.UTF-8 > /etc/container_environment/LC_CTYPE + +# Make init folders +mkdir -p /etc/my_init.d +mkdir -p /etc/my_init.pre_shutdown.d +mkdir -p /etc/my_init.post_shutdown.d +mkdir -p /etc/container_environment +touch /etc/container_environment.sh +touch /etc/container_environment.json +chmod 700 /etc/container_environment + +groupadd -g 8377 docker_env +chown :docker_env /etc/container_environment.sh /etc/container_environment.json +chmod 640 /etc/container_environment.sh /etc/container_environment.json +ln -s /etc/container_environment.sh /etc/profile.d/ \ No newline at end of file diff --git a/util/docker/web/runit/cron/run b/util/docker/web/runit/cron/run new file mode 100644 index 000000000..29a0f3117 --- /dev/null +++ b/util/docker/web/runit/cron/run @@ -0,0 +1,8 @@ +#!/bin/sh + +# Touch cron files to fix 'NUMBER OF HARD LINKS > 1' issue. See https://github.com/phusion/baseimage-docker/issues/198 +touch -c /var/spool/cron/crontabs/* +touch -c /etc/crontab +touch -c /etc/cron.*/* + +exec /usr/sbin/cron -f \ No newline at end of file diff --git a/util/docker/web/runit/nginx/run b/util/docker/web/runit/nginx/run new file mode 100644 index 000000000..88c3a13b0 --- /dev/null +++ b/util/docker/web/runit/nginx/run @@ -0,0 +1,3 @@ +#!/bin/bash + +nginx -g "daemon off;" \ No newline at end of file diff --git a/util/docker/web/runit/php-fpm/run b/util/docker/web/runit/php-fpm/run new file mode 100644 index 000000000..6c28dfe30 --- /dev/null +++ b/util/docker/web/runit/php-fpm/run @@ -0,0 +1,3 @@ +#!/bin/bash + +/usr/sbin/php-fpm7.4 -F --fpm-config /etc/php/7.4/fpm/php-fpm.conf -c /etc/php/7.4/fpm/ \ No newline at end of file diff --git a/util/docker/web/runit/php-worker/run b/util/docker/web/runit/php-worker/run new file mode 100644 index 000000000..cd8f69677 --- /dev/null +++ b/util/docker/web/runit/php-worker/run @@ -0,0 +1,3 @@ +#!/bin/bash + +sudo -E -u azuracast php /var/azuracast/www/util/cli.php queue:process 300 \ No newline at end of file diff --git a/util/docker/web/runit/sftpgo/run b/util/docker/web/runit/sftpgo/run new file mode 100644 index 000000000..59c3fe509 --- /dev/null +++ b/util/docker/web/runit/sftpgo/run @@ -0,0 +1,7 @@ +#!/bin/bash + +echo 'Spinning up SFTP process...' + +cd /var/azuracast/sftpgo + +sudo -E -u azuracast sftpgo --config-dir=/var/azuracast/sftpgo serve > /proc/1/fd/1 2> /proc/1/fd/2 \ No newline at end of file diff --git a/util/docker/web/scripts/azuracast_cli b/util/docker/web/scripts/azuracast_cli new file mode 100644 index 000000000..2f4197d3c --- /dev/null +++ b/util/docker/web/scripts/azuracast_cli @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +if [ `whoami` != 'azuracast' ]; then + echo 'This script must be run as the "azuracast" user. Rerunning...' + sudo -E -u azuracast azuracast_cli "$@" + exit 1 +fi + +cd /var/azuracast/www +php bin/console "$@" \ No newline at end of file diff --git a/util/docker/web/scripts/azuracast_install b/util/docker/web/scripts/azuracast_install new file mode 100644 index 000000000..61d69d0ea --- /dev/null +++ b/util/docker/web/scripts/azuracast_install @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +bool() { + case "$1" in + Y*|y*|true|TRUE|1) return 0 ;; + esac + return 1 +} + +if [ `whoami` != 'azuracast' ]; then + echo 'This script must be run as the "azuracast" user. Rerunning...' + sudo -E -u azuracast azuracast_install $@ + exit 1 +fi + +update_mode=0 +release_update=0 +original_args=$* + +while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in + --update ) + update_mode=1 + ;; + + -r | --release ) + release_update=1 + ;; +esac; shift; done +if [[ "$1" == '--' ]]; then shift; fi + +if bool "$PREFER_RELEASE_BUILDS"; then + release_update=1 +fi + +if [ $update_mode = 1 ]; then + echo "Updating AzuraCast..." +else + echo "Installing AzuraCast..." +fi + +APPLICATION_ENV="${APPLICATION_ENV:-production}" +echo "(Environment: $APPLICATION_ENV)" + +if [ $APPLICATION_ENV = "production" ]; then + if [ $release_update = 1 ]; then + COMPOSER_VERSION=${1:-"^0.9.5"} + composer create-project azuracast/azuracast /var/azuracast/new $COMPOSER_VERSION --prefer-dist --no-install + + rsync -a -v -q /var/azuracast/new/ /var/azuracast/www + rm -rf /var/azuracast/new + + if bool "$COMPOSER_PLUGIN_MODE"; then + composer update --lock --no-dev --optimize-autoloader + else + composer install --no-dev --optimize-autoloader + fi + else + if bool "$COMPOSER_PLUGIN_MODE"; then + composer update --lock --no-dev --optimize-autoloader + fi + fi +else + if [ $APPLICATION_ENV = "testing" ]; then + sudo mkdir -p vendor + sudo chmod -R 0744 vendor + sudo chown -R azuracast:azuracast vendor + fi + + composer install +fi + +azuracast_cli azuracast:setup ${original_args} \ No newline at end of file diff --git a/util/docker/web/scripts/azuracast_migrate_stations b/util/docker/web/scripts/azuracast_migrate_stations new file mode 100644 index 000000000..24c3d01e8 --- /dev/null +++ b/util/docker/web/scripts/azuracast_migrate_stations @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +mv ${1}/* /var/azuracast/stations/ \ No newline at end of file diff --git a/util/docker/web/scripts/azuracast_restore b/util/docker/web/scripts/azuracast_restore new file mode 100644 index 000000000..80ff1d0bb --- /dev/null +++ b/util/docker/web/scripts/azuracast_restore @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +bool() { + case "$1" in + Y*|y*|true|TRUE|1) return 0 ;; + esac + return 1 +} + +if [ `whoami` != 'azuracast' ]; then + echo 'This script must be run as the "azuracast" user. Rerunning...' + sudo -E -u azuracast azuracast_restore $@ + exit 1 +fi + +release_update=0 +original_args=$* + +while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in + -r | --release ) + release_update=1 + ;; +esac; shift; done +if [[ "$1" == '--' ]]; then shift; fi + +if bool "$PREFER_RELEASE_BUILDS"; then + release_update=1 +fi + +echo "Restoring AzuraCast..." + +APPLICATION_ENV="${APPLICATION_ENV:-production}" +echo "(Environment: $APPLICATION_ENV)" + +if [ $APPLICATION_ENV = "production" ]; then + if [ $release_update = 1 ]; then + composer create-project azuracast/azuracast /var/azuracast/new ^0.9.5 --prefer-dist --no-install + else + composer create-project azuracast/azuracast /var/azuracast/new dev-master --prefer-source --keep-vcs --no-install + fi + + rsync -a -v -q /var/azuracast/new/ /var/azuracast/www + rm -rf /var/azuracast/new + + if bool "$COMPOSER_PLUGIN_MODE"; then + composer update --lock --no-dev --optimize-autoloader + else + composer install --no-dev --optimize-autoloader + fi +else + composer install +fi + +azuracast_cli azuracast:restore ${original_args} \ No newline at end of file diff --git a/util/docker/web/scripts/azuracast_sftp_auth b/util/docker/web/scripts/azuracast_sftp_auth new file mode 100644 index 000000000..5e1ff7fd5 --- /dev/null +++ b/util/docker/web/scripts/azuracast_sftp_auth @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source /etc/container_environment.sh + +azuracast_cli azuracast:internal:sftp-auth "$@" \ No newline at end of file diff --git a/util/docker/web/scripts/azuracast_sftp_upload b/util/docker/web/scripts/azuracast_sftp_upload new file mode 100644 index 000000000..3df96234e --- /dev/null +++ b/util/docker/web/scripts/azuracast_sftp_upload @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source /etc/container_environment.sh + +azuracast_cli azuracast:internal:sftp-upload "$@" \ No newline at end of file diff --git a/util/docker/web/scripts/azuracast_update b/util/docker/web/scripts/azuracast_update new file mode 100644 index 000000000..5cc5ecc36 --- /dev/null +++ b/util/docker/web/scripts/azuracast_update @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +azuracast_install --update $* \ No newline at end of file diff --git a/util/docker/web/scripts/cron_task b/util/docker/web/scripts/cron_task new file mode 100644 index 000000000..707e1e332 --- /dev/null +++ b/util/docker/web/scripts/cron_task @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source /etc/container_environment.sh + +sudo -E -u azuracast $@ > /proc/1/fd/1 2> /proc/1/fd/2 \ No newline at end of file diff --git a/util/docker/web/scripts/letsencrypt_connect b/util/docker/web/scripts/letsencrypt_connect new file mode 100644 index 000000000..9c05b1463 --- /dev/null +++ b/util/docker/web/scripts/letsencrypt_connect @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +if [ `whoami` != 'azuracast' ]; then + echo 'This script must be run as the "azuracast" user. Rerunning...' + sudo -E -u azuracast letsencrypt_connect $@ + exit 1 +fi + +usage () +{ + echo 'Usage: letsencrypt_connect domainname.example.com' + exit +} + +if [ "$#" -ne 1 ] +then + usage +fi + +DOMAIN=$1 +SSL_DIR="/etc/letsencrypt" +LETSENCRYPT_DIR="$SSL_DIR/live/$DOMAIN" +shift + +certbot certonly --webroot -w /var/www/letsencrypt -d $DOMAIN $* + +if [ -d $LETSENCRYPT_DIR ]; then + cd $SSL_DIR + rm ssl.crt ssl.key + + ln -s live/$DOMAIN/fullchain.pem ssl.crt + ln -s live/$DOMAIN/privkey.pem ssl.key + + echo 'Reloading nginx...' + sudo kill -HUP `sudo cat /var/run/nginx.pid` + + echo 'Domain is ready to be served via LetsEncrypt!' + exit +else + echo "Domain name $DOMAIN is not set up with LetsEncrypt yet. Reverting to self-signed cert..." + + letsencrypt_uninstall + exit 1 +fi \ No newline at end of file diff --git a/util/docker/web/scripts/letsencrypt_renew b/util/docker/web/scripts/letsencrypt_renew new file mode 100644 index 000000000..d7672ae82 --- /dev/null +++ b/util/docker/web/scripts/letsencrypt_renew @@ -0,0 +1,9 @@ +#!/usr/bin/env sh + +if [ `whoami` != 'azuracast' ]; then + echo 'This script must be run as the "azuracast" user. Rerunning...' + sudo -E -u azuracast letsencrypt_renew $@ + exit 1 +fi + +certbot renew --webroot -w /var/www/letsencrypt $* \ No newline at end of file diff --git a/util/docker/web/scripts/letsencrypt_uninstall b/util/docker/web/scripts/letsencrypt_uninstall new file mode 100644 index 000000000..c1040d1bd --- /dev/null +++ b/util/docker/web/scripts/letsencrypt_uninstall @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +if [ `whoami` != 'azuracast' ]; then + echo 'This script must be run as the "azuracast" user. Rerunning...' + sudo -E -u azuracast letsencrypt_uninstall $@ + exit 1 +fi + +SSL_DIR="/etc/letsencrypt" + +cd $SSL_DIR +rm ssl.crt ssl.key + +openssl req -new -nodes -x509 -subj "/C=US/ST=Texas/L=Austin/O=IT/CN=localhost" \ + -days 365 -extensions v3_ca \ + -keyout $SSL_DIR/selfsigned.key \ + -out $SSL_DIR/selfsigned.crt + +ln -s selfsigned.key ssl.key +ln -s selfsigned.crt ssl.crt + +sudo kill -HUP `sudo cat /var/run/nginx.pid` + +echo "Self-signed certificate restored." +exit \ No newline at end of file diff --git a/util/docker/web/scripts/my_init b/util/docker/web/scripts/my_init new file mode 100644 index 000000000..19ec16258 --- /dev/null +++ b/util/docker/web/scripts/my_init @@ -0,0 +1,420 @@ +#!/usr/bin/python3 -u +# -*- coding: utf-8 -*- + +import argparse +import errno +import json +import os +import os.path +import re +import signal +import stat +import sys +import time + +ENV_INIT_DIRECTORY = os.environ.get('ENV_INIT_DIRECTORY', '/etc/my_init.d') + +KILL_PROCESS_TIMEOUT = int(os.environ.get('KILL_PROCESS_TIMEOUT', 30)) +KILL_ALL_PROCESSES_TIMEOUT = int(os.environ.get('KILL_ALL_PROCESSES_TIMEOUT', 30)) + +LOG_LEVEL_ERROR = 1 +LOG_LEVEL_WARN = 1 +LOG_LEVEL_INFO = 2 +LOG_LEVEL_DEBUG = 3 + +SHENV_NAME_WHITELIST_REGEX = re.compile('\W') + +log_level = None + +terminated_child_processes = {} + +_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search + + +class AlarmException(Exception): + pass + + +def error(message): + if log_level >= LOG_LEVEL_ERROR: + sys.stderr.write("*** %s\n" % message) + + +def warn(message): + if log_level >= LOG_LEVEL_WARN: + sys.stderr.write("*** %s\n" % message) + + +def info(message): + if log_level >= LOG_LEVEL_INFO: + sys.stderr.write("*** %s\n" % message) + + +def debug(message): + if log_level >= LOG_LEVEL_DEBUG: + sys.stderr.write("*** %s\n" % message) + + +def ignore_signals_and_raise_keyboard_interrupt(signame): + signal.signal(signal.SIGTERM, signal.SIG_IGN) + signal.signal(signal.SIGINT, signal.SIG_IGN) + raise KeyboardInterrupt(signame) + + +def raise_alarm_exception(): + raise AlarmException('Alarm') + + +def listdir(path): + try: + result = os.stat(path) + except OSError: + return [] + if stat.S_ISDIR(result.st_mode): + return sorted(os.listdir(path)) + else: + return [] + + +def is_exe(path): + try: + return os.path.isfile(path) and os.access(path, os.X_OK) + except OSError: + return False + + +def import_envvars(clear_existing_environment=True, override_existing_environment=True): + if not os.path.exists("/etc/container_environment"): + return + new_env = {} + for envfile in listdir("/etc/container_environment"): + name = os.path.basename(envfile) + with open("/etc/container_environment/" + envfile, "r") as f: + # Text files often end with a trailing newline, which we + # don't want to include in the env variable value. See + # https://github.com/phusion/baseimage-docker/pull/49 + value = re.sub('\n\Z', '', f.read()) + new_env[name] = value + if clear_existing_environment: + os.environ.clear() + for name, value in new_env.items(): + if override_existing_environment or name not in os.environ: + os.environ[name] = value + + +def export_envvars(to_dir=True): + if not os.path.exists("/etc/container_environment"): + return + shell_dump = "" + for name, value in os.environ.items(): + if name in ['HOME', 'USER', 'GROUP', 'UID', 'GID', 'SHELL']: + continue + if to_dir: + with open("/etc/container_environment/" + name, "w") as f: + f.write(value) + shell_dump += "export " + sanitize_shenvname(name) + "=" + shquote(value) + "\n" + with open("/etc/container_environment.sh", "w") as f: + f.write(shell_dump) + with open("/etc/container_environment.json", "w") as f: + f.write(json.dumps(dict(os.environ))) + + +def shquote(s): + """Return a shell-escaped version of the string *s*.""" + if not s: + return "''" + if _find_unsafe(s) is None: + return s + + # use single quotes, and put single quotes into double quotes + # the string $'b is then quoted as '$'"'"'b' + return "'" + s.replace("'", "'\"'\"'") + "'" + + +def sanitize_shenvname(s): + """Return string with [0-9a-zA-Z_] characters""" + return re.sub(SHENV_NAME_WHITELIST_REGEX, "_", s) + + +# Waits for the child process with the given PID, while at the same time +# reaping any other child processes that have exited (e.g. adopted child +# processes that have terminated). + +def waitpid_reap_other_children(pid): + global terminated_child_processes + + status = terminated_child_processes.get(pid) + if status: + # A previous call to waitpid_reap_other_children(), + # with an argument not equal to the current argument, + # already waited for this process. Return the status + # that was obtained back then. + del terminated_child_processes[pid] + return status + + done = False + status = None + while not done: + try: + # https://github.com/phusion/baseimage-docker/issues/151#issuecomment-92660569 + this_pid, status = os.waitpid(pid, os.WNOHANG) + if this_pid == 0: + this_pid, status = os.waitpid(-1, 0) + if this_pid == pid: + done = True + else: + # Save status for later. + terminated_child_processes[this_pid] = status + except OSError as e: + if e.errno == errno.ECHILD or e.errno == errno.ESRCH: + return None + else: + raise + return status + + +def stop_child_process(name, pid, signo=signal.SIGTERM, time_limit=KILL_PROCESS_TIMEOUT): + info("Shutting down %s (PID %d)..." % (name, pid)) + try: + os.kill(pid, signo) + except OSError: + pass + signal.alarm(time_limit) + try: + try: + waitpid_reap_other_children(pid) + except OSError: + pass + except AlarmException: + warn("%s (PID %d) did not shut down in time. Forcing it to exit." % (name, pid)) + try: + os.kill(pid, signal.SIGKILL) + except OSError: + pass + try: + waitpid_reap_other_children(pid) + except OSError: + pass + finally: + signal.alarm(0) + + +def run_command_killable(*argv): + filename = argv[0] + status = None + pid = os.spawnvp(os.P_NOWAIT, filename, argv) + try: + status = waitpid_reap_other_children(pid) + except BaseException: + warn("An error occurred. Aborting.") + stop_child_process(filename, pid) + raise + if status != 0: + if status is None: + error("%s exited with unknown status\n" % filename) + else: + error("%s failed with status %d\n" % (filename, os.WEXITSTATUS(status))) + sys.exit(1) + + +def run_command_killable_and_import_envvars(*argv): + run_command_killable(*argv) + import_envvars() + export_envvars(False) + + +def kill_all_processes(time_limit): + info("Killing all processes...") + try: + os.kill(-1, signal.SIGTERM) + except OSError: + pass + signal.alarm(time_limit) + try: + # Wait until no more child processes exist. + done = False + while not done: + try: + os.waitpid(-1, 0) + except OSError as e: + if e.errno == errno.ECHILD: + done = True + else: + raise + except AlarmException: + warn("Not all processes have exited in time. Forcing them to exit.") + try: + os.kill(-1, signal.SIGKILL) + except OSError: + pass + finally: + signal.alarm(0) + + +def run_startup_files(): + # Run ENV_INIT_DIRECTORY/* + for name in listdir(ENV_INIT_DIRECTORY): + filename = os.path.join(ENV_INIT_DIRECTORY, name) + if is_exe(filename): + info("Running %s..." % filename) + run_command_killable_and_import_envvars(filename) + + # Run /etc/rc.local. + if is_exe("/etc/rc.local"): + info("Running /etc/rc.local...") + run_command_killable_and_import_envvars("/etc/rc.local") + + +def run_pre_shutdown_scripts(): + debug("Running pre-shutdown scripts...") + + # Run /etc/my_init.pre_shutdown.d/* + for name in listdir("/etc/my_init.pre_shutdown.d"): + filename = "/etc/my_init.pre_shutdown.d/" + name + if is_exe(filename): + info("Running %s..." % filename) + run_command_killable(filename) + + +def run_post_shutdown_scripts(): + debug("Running post-shutdown scripts...") + + # Run /etc/my_init.post_shutdown.d/* + for name in listdir("/etc/my_init.post_shutdown.d"): + filename = "/etc/my_init.post_shutdown.d/" + name + if is_exe(filename): + info("Running %s..." % filename) + run_command_killable(filename) + + +def start_runit(): + info("Booting runit daemon...") + pid = os.spawnl(os.P_NOWAIT, "/usr/bin/runsvdir", "/usr/bin/runsvdir", + "-P", "/etc/service") + info("Runit started as PID %d" % pid) + return pid + + +def wait_for_runit_or_interrupt(pid): + status = waitpid_reap_other_children(pid) + return (True, status) + + +def shutdown_runit_services(quiet=False): + if not quiet: + debug("Begin shutting down runit services...") + os.system("/usr/bin/sv -w %d force-stop /etc/service/* > /dev/null" % KILL_PROCESS_TIMEOUT) + + +def wait_for_runit_services(): + debug("Waiting for runit services to exit...") + done = False + while not done: + done = os.system("/usr/bin/sv status /etc/service/* | grep -q '^run:'") != 0 + if not done: + time.sleep(0.1) + # According to https://github.com/phusion/baseimage-docker/issues/315 + # there is a bug or race condition in Runit, causing it + # not to shutdown services that are already being started. + # So during shutdown we repeatedly instruct Runit to shutdown + # services. + shutdown_runit_services(True) + + +def install_insecure_key(): + info("Installing insecure SSH key for user root") + run_command_killable("/usr/sbin/enable_insecure_key") + + +def main(args): + import_envvars(False, False) + export_envvars() + + if args.enable_insecure_key: + install_insecure_key() + + if not args.skip_startup_files: + run_startup_files() + + runit_exited = False + exit_code = None + + if not args.skip_runit: + runit_pid = start_runit() + try: + exit_status = None + if len(args.main_command) == 0: + runit_exited, exit_code = wait_for_runit_or_interrupt(runit_pid) + if runit_exited: + if exit_code is None: + info("Runit exited with unknown status") + exit_status = 1 + else: + exit_status = os.WEXITSTATUS(exit_code) + info("Runit exited with status %d" % exit_status) + else: + info("Running %s..." % " ".join(args.main_command)) + pid = os.spawnvp(os.P_NOWAIT, args.main_command[0], args.main_command) + try: + exit_code = waitpid_reap_other_children(pid) + if exit_code is None: + info("%s exited with unknown status." % args.main_command[0]) + exit_status = 1 + else: + exit_status = os.WEXITSTATUS(exit_code) + info("%s exited with status %d." % (args.main_command[0], exit_status)) + except KeyboardInterrupt: + stop_child_process(args.main_command[0], pid) + raise + except BaseException: + warn("An error occurred. Aborting.") + stop_child_process(args.main_command[0], pid) + raise + sys.exit(exit_status) + finally: + if not args.skip_runit: + run_pre_shutdown_scripts() + shutdown_runit_services() + if not runit_exited: + stop_child_process("runit daemon", runit_pid) + wait_for_runit_services() + run_post_shutdown_scripts() + +# Parse options. +parser = argparse.ArgumentParser(description='Initialize the system.') +parser.add_argument('main_command', metavar='MAIN_COMMAND', type=str, nargs='*', + help='The main command to run. (default: runit)') +parser.add_argument('--enable-insecure-key', dest='enable_insecure_key', + action='store_const', const=True, default=False, + help='Install the insecure SSH key') +parser.add_argument('--skip-startup-files', dest='skip_startup_files', + action='store_const', const=True, default=False, + help='Skip running /etc/my_init.d/* and /etc/rc.local') +parser.add_argument('--skip-runit', dest='skip_runit', + action='store_const', const=True, default=False, + help='Do not run runit services') +parser.add_argument('--no-kill-all-on-exit', dest='kill_all_on_exit', + action='store_const', const=False, default=True, + help='Don\'t kill all processes on the system upon exiting') +parser.add_argument('--quiet', dest='log_level', + action='store_const', const=LOG_LEVEL_WARN, default=LOG_LEVEL_INFO, + help='Only print warnings and errors') +args = parser.parse_args() +log_level = args.log_level + +if args.skip_runit and len(args.main_command) == 0: + error("When --skip-runit is given, you must also pass a main command.") + sys.exit(1) + +# Run main function. +signal.signal(signal.SIGTERM, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt('SIGTERM')) +signal.signal(signal.SIGINT, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt('SIGINT')) +signal.signal(signal.SIGALRM, lambda signum, frame: raise_alarm_exception()) +try: + main(args) +except KeyboardInterrupt: + warn("Init system aborted.") + exit(2) +finally: + if args.kill_all_on_exit: + kill_all_processes(KILL_ALL_PROCESSES_TIMEOUT) \ No newline at end of file diff --git a/util/docker/web/scripts/temp_cleanup b/util/docker/web/scripts/temp_cleanup new file mode 100644 index 000000000..b4e5038f7 --- /dev/null +++ b/util/docker/web/scripts/temp_cleanup @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +/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 diff --git a/util/docker/web/setup.sh b/util/docker/web/setup.sh new file mode 100644 index 000000000..6084adb94 --- /dev/null +++ b/util/docker/web/setup.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +# Install common scripts +cp -rT /bd_build/scripts/ /usr/local/bin +chmod -R a+x /usr/local/bin + +# Install runit +$minimal_apt_get_install runit + +# Install runit scripts +cp -rT /bd_build/startup_scripts/. /etc/my_init.d/ +cp -rT /bd_build/runit/. /etc/service/ + +chmod -R +x /etc/service +chmod -R +x /etc/my_init.d + +# Install scripts commonly used during setup. +$minimal_apt_get_install curl wget tar zip unzip git rsync tzdata gpg-agent openssh-client + +# Run service setup for all setup scripts +for f in /bd_build/setup/*.sh; do + bash "$f" -H +done \ No newline at end of file diff --git a/util/docker/web/setup/certbot.sh b/util/docker/web/setup/certbot.sh new file mode 100644 index 000000000..82b97a462 --- /dev/null +++ b/util/docker/web/setup/certbot.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +add-apt-repository -y ppa:certbot/certbot + +apt-get update + +$minimal_apt_get_install certbot openssl + +mkdir -p /var/www/letsencrypt /var/lib/letsencrypt /etc/letsencrypt/renewal-hooks/deploy /var/log/letsencrypt +chown -R azuracast:azuracast /var/www/letsencrypt /var/lib/letsencrypt /etc/letsencrypt /var/log/letsencrypt + +# SSL self-signed cert generation +openssl req -new -nodes -x509 -subj "/C=US/ST=Texas/L=Austin/O=IT/CN=localhost" \ + -days 365 -extensions v3_ca \ + -keyout /etc/letsencrypt/selfsigned.key \ + -out /etc/letsencrypt/selfsigned.crt + +ln -s /etc/letsencrypt/selfsigned.key /etc/letsencrypt/ssl.key +ln -s /etc/letsencrypt/selfsigned.crt /etc/letsencrypt/ssl.crt + +# Add nginx restart hook. +cp /bd_build/letsencrypt/01-reload-nginx /etc/letsencrypt/renewal-hooks/deploy/01-reload-nginx +chmod a+x /etc/letsencrypt/renewal-hooks/deploy/* \ No newline at end of file diff --git a/util/docker/web/setup/cron.sh b/util/docker/web/setup/cron.sh new file mode 100644 index 000000000..0e2bc2dae --- /dev/null +++ b/util/docker/web/setup/cron.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +$minimal_apt_get_install cron + +service cron stop + +chmod 600 /etc/crontab + +# Fix cron issues in 0.9.19, see also #345: https://github.com/phusion/baseimage-docker/issues/345 +sed -i 's/^\s*session\s\+required\s\+pam_loginuid.so/# &/' /etc/pam.d/cron + +## Remove useless cron entries. +# Checks for lost+found and scans for mtab. +rm -f /etc/cron.daily/standard +rm -f /etc/cron.daily/upstart +rm -f /etc/cron.daily/dpkg +rm -f /etc/cron.daily/password +rm -f /etc/cron.weekly/fstrim + +cp -r /bd_build/cron/. /etc/cron.d/ +chmod -R 600 /etc/cron.d/* \ No newline at end of file diff --git a/util/docker/web/setup/dockerize.sh b/util/docker/web/setup/dockerize.sh new file mode 100644 index 000000000..fabfea303 --- /dev/null +++ b/util/docker/web/setup/dockerize.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +DOCKERIZE_VERSION="v0.6.1" + +cd /tmp + +wget --quiet https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz + +tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz +rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ No newline at end of file diff --git a/util/docker/web/setup/influxdb.sh b/util/docker/web/setup/influxdb.sh new file mode 100644 index 000000000..eb80bcd12 --- /dev/null +++ b/util/docker/web/setup/influxdb.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +wget -qO- https://repos.influxdata.com/influxdb.key | apt-key add - +echo "deb https://repos.influxdata.com/ubuntu bionic stable" | tee /etc/apt/sources.list.d/influxdb.list + +apt-get update + +$minimal_apt_get_install influxdb \ No newline at end of file diff --git a/util/docker/web/setup/nginx.sh b/util/docker/web/setup/nginx.sh new file mode 100644 index 000000000..4d213d853 --- /dev/null +++ b/util/docker/web/setup/nginx.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +$minimal_apt_get_install nginx nginx-common nginx-extras + +# Install nginx and configuration +cp /bd_build/nginx/nginx.conf /etc/nginx/nginx.conf +cp /bd_build/nginx/azuracast.conf /etc/nginx/conf.d/azuracast.conf + +# Create nginx temp dirs +mkdir -p /tmp/azuracast_nginx_client /tmp/azuracast_fastcgi_temp +touch /tmp/azuracast_nginx_client/.tmpreaper +touch /tmp/azuracast_fastcgi_temp/.tmpreaper +chmod -R 777 /tmp/azuracast_* + +# Generate the dhparam.pem file (takes a long time) +openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096 \ No newline at end of file diff --git a/util/docker/web/setup/php.sh b/util/docker/web/setup/php.sh new file mode 100644 index 000000000..f0b1a7480 --- /dev/null +++ b/util/docker/web/setup/php.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +add-apt-repository -y ppa:ondrej/php +apt-get update + +$minimal_apt_get_install php7.4-fpm php7.4-cli php7.4-gd \ + php7.4-curl php7.4-xml php7.4-zip php7.4-bcmath \ + php7.4-mysqlnd php7.4-mbstring php7.4-intl php7.4-redis \ + mariadb-client + +# Copy PHP configuration +mkdir -p /run/php +touch /run/php/php7.4-fpm.pid + +cp /bd_build/php/php.ini.tmpl /etc/php/7.4/fpm/05-azuracast.ini.tmpl +cp /bd_build/php/www.conf.tmpl /etc/php/7.4/fpm/www.conf.tmpl + +# Install Composer +curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer \ No newline at end of file diff --git a/util/docker/web/setup/sftpgo.sh b/util/docker/web/setup/sftpgo.sh new file mode 100644 index 000000000..3de008b70 --- /dev/null +++ b/util/docker/web/setup/sftpgo.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +mkdir -p /var/azuracast/sftpgo/persist /var/azuracast/sftpgo/backups + +cp /bd_build/sftpgo/sftpgo.json /var/azuracast/sftpgo/sftpgo.json + +touch /var/azuracast/sftpgo/sftpgo.db +chown -R azuracast:azuracast /var/azuracast/sftpgo \ No newline at end of file diff --git a/util/docker/web/setup/tmpreaper.sh b/util/docker/web/setup/tmpreaper.sh new file mode 100644 index 000000000..28992225f --- /dev/null +++ b/util/docker/web/setup/tmpreaper.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +source /bd_build/buildconfig +set -x + +$minimal_apt_get_install tmpreaper \ No newline at end of file diff --git a/util/docker/web/sftpgo/sftpgo.json b/util/docker/web/sftpgo/sftpgo.json new file mode 100644 index 000000000..3530ca2f4 --- /dev/null +++ b/util/docker/web/sftpgo/sftpgo.json @@ -0,0 +1,33 @@ +{ + "sftpd": { + "bind_port": 2022, + "bind_address": "", + "idle_timeout": 15, + "max_auth_tries": 0, + "umask": "0022", + "banner": "", + "upload_mode": 1, + "actions": { + "execute_on": ["upload"], + "command": "/usr/local/bin/azuracast_sftp_upload" + }, + "keys": [ + {"private_key": "persist/id_rsa"} + ], + "enable_scp": true + }, + "data_provider": { + "driver": "bolt", + "name": "sftpgo.db", + "users_base_dir": "/var/azuracast/stations", + "external_auth_program": "/usr/local/bin/azuracast_sftp_auth", + "external_auth_scope": 0 + }, + "httpd": { + "bind_port": 0, + "bind_address": "", + "templates_path": "templates", + "static_files_path": "static", + "backups_path": "backups" + } +} \ No newline at end of file diff --git a/util/docker/web/startup_scripts/php.sh b/util/docker/web/startup_scripts/php.sh new file mode 100644 index 000000000..c76d7aa73 --- /dev/null +++ b/util/docker/web/startup_scripts/php.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Copy the php.ini template to its destination. +dockerize -template "/etc/php/7.4/fpm/05-azuracast.ini.tmpl:/etc/php/7.4/fpm/conf.d/05-azuracast.ini" -template "/etc/php/7.4/fpm/www.conf.tmpl:/etc/php/7.4/fpm/pool.d/www.conf" cp /etc/php/7.4/fpm/conf.d/05-azuracast.ini /etc/php/7.4/cli/conf.d/05-azuracast.ini \ No newline at end of file diff --git a/util/docker/web/startup_scripts/php_workers.sh b/util/docker/web/startup_scripts/php_workers.sh new file mode 100644 index 000000000..e949f34ed --- /dev/null +++ b/util/docker/web/startup_scripts/php_workers.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Duplicate php-worker runit script based on environment variable + +echo "Adding $ADDITIONAL_MEDIA_SYNC_WORKER_COUNT additional workers" + +for ((WORKER_NUMBER=1; WORKER_NUMBER<=$ADDITIONAL_MEDIA_SYNC_WORKER_COUNT; WORKER_NUMBER++)); do + echo "Adding worker $WORKER_NUMBER..." + cp -r /etc/service/php-worker /etc/service/php-worker-"$WORKER_NUMBER" +done + +echo "Done" diff --git a/util/docker/web/startup_scripts/sftpgo_private_key.sh b/util/docker/web/startup_scripts/sftpgo_private_key.sh new file mode 100644 index 000000000..9b30d8fa1 --- /dev/null +++ b/util/docker/web/startup_scripts/sftpgo_private_key.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ ! -f /var/azuracast/sftpgo/persist/id_rsa ]]; then + ssh-keygen -t rsa -b 4096 -f /var/azuracast/sftpgo/persist/id_rsa -q -N "" +fi + +chown -R azuracast:azuracast /var/azuracast/sftpgo/persist \ No newline at end of file