Add web container Docker code to this repo; tie image to code version.

This commit is contained in:
Buster "Silver Eagle" Neece 2020-01-20 22:24:45 -06:00
parent 5bf69298b4
commit 153f7a8ec7
No known key found for this signature in database
GPG Key ID: 6D9E12FF03411F4E
44 changed files with 1349 additions and 1 deletions

17
.dockerignore Normal file
View File

@ -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

84
Dockerfile Normal file
View File

@ -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"]

View File

@ -18,7 +18,7 @@ services:
web:
image: azuracast/azuracast_web_v2:latest
build:
context: ../docker-azuracast-web-v2
context: .
depends_on:
- mariadb
- influxdb

View File

@ -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

View File

@ -0,0 +1,4 @@
export LC_ALL=C
export DEBIAN_FRONTEND=noninteractive
minimal_apt_get_install='apt-get install -y --no-install-recommends'

View File

@ -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*

View File

@ -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

View File

@ -0,0 +1,4 @@
#! /bin/bash
set -e
sudo kill -HUP `sudo cat /var/run/nginx.pid`

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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}}

View File

@ -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

View File

@ -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/

View File

@ -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

View File

@ -0,0 +1,3 @@
#!/bin/bash
nginx -g "daemon off;"

View File

@ -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/

View File

@ -0,0 +1,3 @@
#!/bin/bash
sudo -E -u azuracast php /var/azuracast/www/util/cli.php queue:process 300

View File

@ -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

View File

@ -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 "$@"

View File

@ -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}

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
mv ${1}/* /var/azuracast/stations/

View File

@ -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}

View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
source /etc/container_environment.sh
azuracast_cli azuracast:internal:sftp-auth "$@"

View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
source /etc/container_environment.sh
azuracast_cli azuracast:internal:sftp-upload "$@"

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
azuracast_install --update $*

View File

@ -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

View File

@ -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

View File

@ -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 $*

View File

@ -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

View File

@ -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)

View File

@ -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

26
util/docker/web/setup.sh Normal file
View File

@ -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

View File

@ -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/*

View File

@ -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/*

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,6 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
$minimal_apt_get_install tmpreaper

View File

@ -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"
}
}

View File

@ -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

View File

@ -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"

View File

@ -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