diff --git a/srv/azuracast/.env b/srv/azuracast/.env new file mode 100644 index 0000000..5aa1aa5 --- /dev/null +++ b/srv/azuracast/.env @@ -0,0 +1,9 @@ +COMPOSE_PROJECT_NAME=azuracast + +AZURACAST_HTTP_PORT=80 +AZURACAST_HTTPS_PORT=443 + +AZURACAST_SFTP_PORT=2022 +AZURACAST_VERSION=latest +LETSENCRYPT_HOST=ac.accroc.radio +LETSENCRYPT_EMAIL= diff --git a/srv/azuracast/azuracast.env b/srv/azuracast/azuracast.env new file mode 100644 index 0000000..9d6975b --- /dev/null +++ b/srv/azuracast/azuracast.env @@ -0,0 +1,112 @@ +# +# AzuraCast Customization +# + +# The application environment. +# Valid options: production, development, testing +APPLICATION_ENV=production + +# Manually modify the logging level. +# This allows you to log debug-level errors temporarily (for problem-solving) or reduce +# the volume of logs that are produced by your installation, without needing to modify +# whether your installation is a production or development instance. +# Valid options: debug, info, notice, warning, error, critical, alert, emergency +# LOG_LEVEL=notice + +# Enable certain advanced features inside the web interface, including +# advanced playlist coniguration, station port assignment, changing base +# media directory, and other functionality that should only be used by +# users who are comfortable with advanced functionality. +# +# Disclaimer: If this is enabled, you are responsible for fixing any errors +# that are caused by your misuse of advanced features! In many cases, the easiest +# fix is to revert your own changes. +# +# Valid options: true, false +ENABLE_ADVANCED_FEATURES=false + +# Enable the composer "merge" functionality to combine the main application's +# composer.json file with any plugins' composer files. +# This can have performance implications, so you should only use it if +# you use one or more plugins with their own Composer dependencies. +# Valid options: true, false +COMPOSER_PLUGIN_MODE=false + +# The minimum port number to use when automatically assigning ports to a station. +# By default, this matches the first forwarded port on the "stations" container. +# You can modify this variable if your station port range is different. +# Be sure to also forward the necessary ports via `docker-compose.yml` +# (and nginx, if you want to use the built-in port-80/443 proxy)! +AUTO_ASSIGN_PORT_MIN=8000 + +# The maximum port number to use when automatically assigning ports to a station. +# See AUTO_ASSIGN_PORT_MIN. +AUTO_ASSIGN_PORT_MAX=8499 + +# +# Database Configuration +# -- +# Once the database has been installed, DO NOT CHANGE these values! +# + +# The host to connect to. Leave this as the default value unless you're connecting +# to an external database server. +# Default: mariadb +MYSQL_HOST=mariadb + +# The port to connect to. Leave this as the default value unless you're connecting +# to an external database server. +# Default: 3306 +MYSQL_PORT=3306 + +# The username AzuraCast will use to connect to the database. +# Default: azuracast +MYSQL_USER=azuracast + +# The password AzuraCast will use to connect to the database. +# By default, the database is not exposed to the Internet at all and this is only +# an internal password used by the service itself. +# Default: 0000000000000000000000000 +MYSQL_PASSWORD=00000000000000000000000000 + +# The name of the AzuraCast database. +# Default: azuracast +MYSQL_DATABASE=azuracast + +# Automatically generate a random root password upon the first database spin-up. +# This password will be visible in the mariadb container's logs. +# Default: yes +MYSQL_RANDOM_ROOT_PASSWORD=yes + +# Log slower queries for the purpose of diagnosing issues. Only turn this on when +# you need to, by uncommenting this and switching it to 1. +# To read the slow query log once enabled, run: +# docker-compose exec mariadb slow_queries +# Default: 0 +MYSQL_SLOW_QUERY_LOG=0 + +# +# Advanced Configuration +# + +# Override the IP/hostname to use when negotiating inbound FTP Passive Mode (PASV) connections. +# The system will attempt to automatically detect this, so you often don't need to change it. +# FTP_PASV_IP=localhost + +# PHP's maximum POST body size and max upload filesize. +# PHP_MAX_FILE_SIZE=25M + +# PHP's maximum memory limit. +# PHP_MEMORY_LIMIT=128M + +# PHP's maximum script execution time (in seconds). +# PHP_MAX_EXECUTION_TIME=30 + +# Maximum number of PHP-FPM worker processes to spawn. +# PHP_FPM_MAX_CHILDREN=5 + +# Create additional media sync worker processes. +# This setting can be used to increase the performance of the media sync process +# by creating additional worker processes to consume messages +# Default: 0 +# ADDITIONAL_MEDIA_SYNC_WORKER_COUNT=0 diff --git a/srv/azuracast/docker-compose.override.yml b/srv/azuracast/docker-compose.override.yml new file mode 100644 index 0000000..299f6fc --- /dev/null +++ b/srv/azuracast/docker-compose.override.yml @@ -0,0 +1,6 @@ +version: '2.2' + +services: + stations: + volumes: + - /srv/azuracast/media/musica-default.mp3:/usr/local/share/icecast/web/error.mp3 diff --git a/srv/azuracast/docker-compose.yml b/srv/azuracast/docker-compose.yml new file mode 100644 index 0000000..78787d4 --- /dev/null +++ b/srv/azuracast/docker-compose.yml @@ -0,0 +1,294 @@ +# +# AzuraCast Docker Compose Configuration File +# +# When updating, you will be prompted to replace this file with a new +# version; you should do this whenever possible to take advantage of +# new updates. +# +# If you need to customize this file, you can create a new file named: +# docker-compose.override.yml +# with any changes you need to make. +# +version: '2.2' + +services: + nginx_proxy: + image: "azuracast/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: + DEFAULT_HOST: ${LETSENCRYPT_HOST:-azuracast.local} + networks: + - frontend + depends_on: + - web + restart: always + + nginx_proxy_letsencrypt: + image: jrcs/letsencrypt-nginx-proxy-companion:latest + volumes_from: + - nginx_proxy + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + DEFAULT_EMAIL: ${LETSENCRYPT_EMAIL} + networks: + - frontend + restart: always + + web: + container_name: azuracast_web + image: "azuracast/azuracast_web_v2:${AZURACAST_VERSION:-latest}" + # Want to customize the HTTP/S ports? Follow the instructions here: + # https://www.azuracast.com/help/docker/#use-non-standard-ports + ports: + - '${AZURACAST_SFTP_PORT:-2022}:2022' + depends_on: + - mariadb + - stations + - redis + env_file: azuracast.env + environment: + LANG: ${LANG:-en_US.UTF-8} + AZURACAST_DC_REVISION: 11 + AZURACAST_VERSION: ${AZURACAST_VERSION:-latest} + AZURACAST_SFTP_PORT: ${AZURACAST_SFTP_PORT:-2022} + VIRTUAL_HOST: ${LETSENCRYPT_HOST:-azuracast.local} + LETSENCRYPT_HOST: ${LETSENCRYPT_HOST} + LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL} + volumes: + - letsencrypt:/etc/nginx/certs:ro + - www_vendor:/var/azuracast/www/vendor + - tmp_data:/var/azuracast/www_tmp + - station_data:/var/azuracast/stations + - shoutcast2_install:/var/azuracast/servers/shoutcast2 + - geolite_install:/var/azuracast/geoip + - sftpgo_data:/var/azuracast/sftpgo/persist + - backups:/var/azuracast/backups + networks: + - frontend + - backend + restart: always + ulimits: &default-ulimits + nofile: + soft: 65536 + hard: 65536 + logging: &default-logging + options: + max-size: "1m" + max-file: "5" + + mariadb: + image: "azuracast/azuracast_db:${AZURACAST_VERSION:-latest}" + volumes: + - db_data:/var/lib/mysql + env_file: azuracast.env + networks: + - backend + restart: always + logging: *default-logging + + redis: + image: "azuracast/azuracast_redis:${AZURACAST_VERSION:-latest}" + sysctls: + net.core.somaxconn: 1024 + volumes: + - redis_data:/data + networks: + - backend + restart: always + logging: *default-logging + + stations: + container_name: azuracast_stations + image: "azuracast/azuracast_radio:${AZURACAST_VERSION:-latest}" + 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: + # https://www.azuracast.com/help/docker/#expand-the-docker-radio-station-port-range + - '8000:8000' + - '8005:8005' + - '8006:8006' + - '8010:8010' + - '8015:8015' + - '8016:8016' + - '8020:8020' + - '8025:8025' + - '8026:8026' + - '8030:8030' + - '8035:8035' + - '8036:8036' + - '8040:8040' + - '8045:8045' + - '8046:8046' + - '8050:8050' + - '8055:8055' + - '8056:8056' + - '8060:8060' + - '8065:8065' + - '8066:8066' + - '8070:8070' + - '8075:8075' + - '8076:8076' + - '8090:8090' + - '8095:8095' + - '8096:8096' + - '8100:8100' + - '8105:8105' + - '8106:8106' + - '8110:8110' + - '8115:8115' + - '8116:8116' + - '8120:8120' + - '8125:8125' + - '8126:8126' + - '8130:8130' + - '8135:8135' + - '8136:8136' + - '8140:8140' + - '8145:8145' + - '8146:8146' + - '8150:8150' + - '8155:8155' + - '8156:8156' + - '8160:8160' + - '8165:8165' + - '8166:8166' + - '8170:8170' + - '8175:8175' + - '8176:8176' + - '8180:8180' + - '8185:8185' + - '8186:8186' + - '8190:8190' + - '8195:8195' + - '8196:8196' + - '8200:8200' + - '8205:8205' + - '8206:8206' + - '8210:8210' + - '8215:8215' + - '8216:8216' + - '8220:8220' + - '8225:8225' + - '8226:8226' + - '8230:8230' + - '8235:8235' + - '8236:8236' + - '8240:8240' + - '8245:8245' + - '8246:8246' + - '8250:8250' + - '8255:8255' + - '8256:8256' + - '8260:8260' + - '8265:8265' + - '8266:8266' + - '8270:8270' + - '8275:8275' + - '8276:8276' + - '8280:8280' + - '8285:8285' + - '8286:8286' + - '8290:8290' + - '8295:8295' + - '8296:8296' + - '8300:8300' + - '8305:8305' + - '8306:8306' + - '8310:8310' + - '8315:8315' + - '8316:8316' + - '8320:8320' + - '8325:8325' + - '8326:8326' + - '8330:8330' + - '8335:8335' + - '8336:8336' + - '8340:8340' + - '8345:8345' + - '8346:8346' + - '8350:8350' + - '8355:8355' + - '8356:8356' + - '8360:8360' + - '8365:8365' + - '8366:8366' + - '8370:8370' + - '8375:8375' + - '8376:8376' + - '8380:8380' + - '8385:8385' + - '8386:8386' + - '8390:8390' + - '8395:8395' + - '8396:8396' + - '8400:8400' + - '8405:8405' + - '8406:8406' + - '8410:8410' + - '8415:8415' + - '8416:8416' + - '8420:8420' + - '8425:8425' + - '8426:8426' + - '8430:8430' + - '8435:8435' + - '8436:8436' + - '8440:8440' + - '8445:8445' + - '8446:8446' + - '8450:8450' + - '8455:8455' + - '8456:8456' + - '8460:8460' + - '8465:8465' + - '8466:8466' + - '8470:8470' + - '8475:8475' + - '8476:8476' + - '8480:8480' + - '8485:8485' + - '8486:8486' + - '8490:8490' + - '8495:8495' + - '8496:8496' + volumes: + - station_data:/var/azuracast/stations + - shoutcast2_install:/var/azuracast/servers/shoutcast2 + - letsencrypt:/etc/nginx/certs:ro + - tmp_data:/var/azuracast/www_tmp + networks: + - frontend + - backend + init: true + restart: always + ulimits: *default-ulimits + logging: *default-logging + +networks: + frontend: + driver: bridge + backend: + driver: bridge + +volumes: + nginx_proxy_vhosts: { } + db_data: { } + letsencrypt: { } + letsencrypt_html: { } + shoutcast2_install: { } + geolite_install: { } + sftpgo_data: { } + station_data: { } + www_vendor: { } + tmp_data: { } + redis_data: { } + backups: { } diff --git a/srv/azuracast/docker.sh b/srv/azuracast/docker.sh new file mode 100755 index 0000000..a44a180 --- /dev/null +++ b/srv/azuracast/docker.sh @@ -0,0 +1,586 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2145,SC2178,SC2120,SC2162 + +# Functions to manage .env files +__dotenv= +__dotenv_file= +__dotenv_cmd=.env + +.env() { + REPLY=() + [[ $__dotenv_file || ${1-} == -* ]] || .env.--file .env || return + if declare -F -- ".env.${1-}" >/dev/null; then + .env."$@" + return + fi + return 64 +} + +.env.-f() { .env.--file "$@"; } + +.env.get() { + .env::arg "get requires a key" "$@" && + [[ "$__dotenv" =~ ^(.*(^|$'\n'))([ ]*)"$1="(.*)$ ]] && + REPLY=${BASH_REMATCH[4]%%$'\n'*} && REPLY=${REPLY%"${REPLY##*[![:space:]]}"} +} + +.env.parse() { + local line key + while IFS= read -r line; do + line=${line#"${line%%[![:space:]]*}"} # trim leading whitespace + line=${line%"${line##*[![:space:]]}"} # trim trailing whitespace + if [[ ! "$line" || "$line" == '#'* ]]; then continue; fi + if (($#)); then + for key; do + if [[ $key == "${line%%=*}" ]]; then + REPLY+=("$line") + break + fi + done + else + REPLY+=("$line") + fi + done <<<"$__dotenv" + ((${#REPLY[@]})) +} + +.env.export() { ! .env.parse "$@" || export "${REPLY[@]}"; } + +.env.set() { + .env::file load || return + local key saved=$__dotenv + while (($#)); do + key=${1#+} + key=${key%%=*} + if .env.get "$key"; then + REPLY=() + if [[ $1 == +* ]]; then + shift + continue # skip if already found + elif [[ $1 == *=* ]]; then + __dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[3]}$1$'\n'${BASH_REMATCH[4]#*$'\n'} + else + __dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[4]#*$'\n'} + continue # delete all occurrences + fi + elif [[ $1 == *=* ]]; then + __dotenv+="${1#+}"$'\n' + fi + shift + done + [[ $__dotenv == "$saved" ]] || .env::file save +} + +.env.puts() { echo "${1-}" >>"$__dotenv_file" && __dotenv+="$1"$'\n'; } + +.env.generate() { + .env::arg "key required for generate" "$@" || return + .env.get "$1" && return || REPLY=$("${@:2}") || return + .env::one "generate: ouptut of '${*:2}' has more than one line" "$REPLY" || return + .env.puts "$1=$REPLY" +} + +.env.--file() { + .env::arg "filename required for --file" "$@" || return + __dotenv_file=$1 + .env::file load || return + (($# < 2)) || .env "${@:2}" +} + +.env::arg() { [[ "${2-}" ]] || { + echo "$__dotenv_cmd: $1" >&2 + return 64 +}; } + +.env::one() { [[ "$2" != *$'\n'* ]] || .env::arg "$1"; } + +.env::file() { + local REPLY=$__dotenv_file + case "$1" in + load) + __dotenv= + ! [[ -f "$REPLY" ]] || __dotenv="$(<"$REPLY")"$'\n' || return + ;; + save) + if [[ -L "$REPLY" ]] && declare -F -- realpath.resolved >/dev/null; then + realpath.resolved "$REPLY" + fi + { [[ ! -f "$REPLY" ]] || cp -p "$REPLY" "$REPLY.bak"; } && + printf %s "$__dotenv" >"$REPLY.bak" && mv "$REPLY.bak" "$REPLY" + ;; + esac +} + +# This is a general-purpose function to ask Yes/No questions in Bash, either +# with or without a default answer. It keeps repeating the question until it +# gets a valid answer. +ask() { + # https://djm.me/ask + local prompt default reply + + while true; do + + if [[ "${2:-}" == "Y" ]]; then + prompt="Y/n" + default=Y + elif [[ "${2:-}" == "N" ]]; then + prompt="y/N" + default=N + else + prompt="y/n" + default= + fi + + # Ask the question (not using "read -p" as it uses stderr not stdout) + echo -n "$1 [$prompt] " + + read reply + + # Default? + if [[ -z "$reply" ]]; then + reply=${default} + fi + + # Check if the reply is valid + case "$reply" in + Y* | y*) return 0 ;; + N* | n*) return 1 ;; + esac + + done +} + +# Generate a prompt to set an environment file value. +envfile-set() { + local VALUE INPUT + + .env --file .env + + .env get "$1" + VALUE=${REPLY:-$2} + + echo -n "$3 [$VALUE]: " + read INPUT + + VALUE=${INPUT:-$VALUE} + + .env set "${1}=${VALUE}" +} + +# +# Configure the ports used by AzuraCast. +# +setup-ports() { + envfile-set "AZURACAST_HTTP_PORT" "80" "Port to use for HTTP connections" + envfile-set "AZURACAST_HTTPS_PORT" "443" "Port to use for HTTPS connections" + envfile-set "AZURACAST_SFTP_PORT" "2022" "Port to use for SFTP connections" +} + +# +# Configure the settings used by LetsEncrypt. +# +setup-letsencrypt() { + envfile-set "LETSENCRYPT_HOST" "" "Domain name (example.com) or names (example.com,foo.bar) to use with LetsEncrypt" + envfile-set "LETSENCRYPT_EMAIL" "" "Optional e-mail address for expiration updates" +} + +# +# Configure release mode settings. +# +setup-release() { + local AZURACAST_VERSION="latest" + if ask "Prefer stable release versions of AzuraCast?" N; then + AZURACAST_VERSION="stable" + fi + + .env --file .env set AZURACAST_VERSION=${AZURACAST_VERSION} +} + +# +# Run the initial installer of Docker and AzuraCast. +# Usage: ./docker.sh install +# +install() { + if [[ ! $(command -v curl) ]]; then + echo "cURL does not appear to be installed." + echo "Install curl using your host's package manager," + echo "then continue installing using this script." + exit 1 + fi + + if [[ $(command -v docker) && $(docker --version) ]]; then + echo "Docker is already installed! Continuing..." + else + if ask "Docker does not appear to be installed. Install Docker now?" Y; then + curl -fsSL get.docker.com -o get-docker.sh + sh get-docker.sh + rm get-docker.sh + + if [[ $EUID -ne 0 ]]; then + sudo usermod -aG docker "$(whoami)" + + echo "You must log out or restart to apply necessary Docker permissions changes." + echo "Restart, then continue installing using this script." + exit + fi + fi + fi + + if [[ $(command -v docker-compose) && $(docker-compose --version) ]]; then + echo "Docker Compose is already installed! Continuing..." + else + if ask "Docker Compose does not appear to be installed. Install Docker Compose now?" Y; then + local COMPOSE_VERSION=1.25.3 + + if [[ $EUID -ne 0 ]]; then + if [[ ! $(command -v sudo) ]]; then + echo "Sudo does not appear to be installed." + echo "Install sudo using your host's package manager," + echo "then continue installing using this script." + exit 1 + fi + + sudo sh -c "curl -fsSL https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose" + sudo chmod +x /usr/local/bin/docker-compose + sudo sh -c "curl -fsSL https://raw.githubusercontent.com/docker/compose/${COMPOSE_VERSION}/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose" + else + curl -fsSL https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + curl -fsSL https://raw.githubusercontent.com/docker/compose/${COMPOSE_VERSION}/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose + fi + fi + fi + + if [[ ! -f .env ]]; then + echo "Writing default .env file..." + curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/master/sample.env -o .env + fi + + if [[ ! -f azuracast.env ]]; then + echo "Creating default AzuraCast settings file..." + curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/master/azuracast.sample.env -o azuracast.env + + # Generate a random password and replace the MariaDB password with it. + local NEW_PASSWORD + NEW_PASSWORD=$( + tr