Implement PHP Docker Installer (#4377)

This commit is contained in:
Buster "Silver Eagle" Neece 2021-07-08 15:03:54 -05:00 committed by GitHub
parent 18eb64c61f
commit ef7989fcfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1226 additions and 236 deletions

View File

@ -11,4 +11,7 @@
!web
!templates
!plugins
!crowdin.yaml
!crowdin.yaml
!docker-compose.sample.yml
!sample.env
!azuracast.sample.env

View File

@ -7,10 +7,14 @@ LOG_LEVEL=debug
ENABLE_ADVANCED_FEATURES=true
COMPOSER_PLUGIN_MODE=false
# Limit station port range
AUTO_ASSIGN_PORT_MIN=8000
AUTO_ASSIGN_PORT_MAX=8099
# Developer options.
# Populate these!
INIT_BASE_URL=http://azuracast.local
INIT_INSTANCE_NAME=local development
INIT_INSTANCE_NAME="local development"
INIT_DEMO_API_KEY=
INIT_ADMIN_EMAIL=
INIT_ADMIN_PASSWORD=
@ -21,66 +25,8 @@ INIT_PODCASTS_PATH=/var/azuracast/www/util/fixtures/init_podcasts
INIT_GEOLITE_LICENSE_KEY=
#
# 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: azur4c457
MYSQL_PASSWORD=azur4c457
# 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
# Debugging
MYSQL_SLOW_QUERY_LOG=1
# Set the amount of allowed connections to the database. This value should be increased
# if you are seeing the `Too many connections` error in the logs.
# Default: 100
MYSQL_MAX_CONNECTIONS=100
# Enable the profiling extension.
# Profiling data can be viewed by visiting http://your-azuracast-site/?SPX_KEY=dev&SPX_UI_URI=/
# Default: 0
PROFILING_EXTENSION_ENABLED=1
# Profile ALL requests made to this account.
# This will have significant performance impact on your installation and should only be used in test circumstances.
# Default: 0
PROFILING_EXTENSION_ALWAYS_ON=0
# Configure the value for the SPX_KEY parameter needed to access the profiling dashboard
# Default: dev
PROFILING_EXTENSION_HTTP_KEY=dev
# Configure the IP whitelist for the profiling dashboard
# Default: *
PROFILING_EXTENSION_HTTP_IP_WHITELIST=*

View File

@ -105,10 +105,6 @@ MYSQL_MAX_CONNECTIONS=100
# 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
@ -127,12 +123,6 @@ MYSQL_MAX_CONNECTIONS=100
# 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
#
# PHP-SPX profiling extension Configuration
#

22
bin/installer Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env php
<?php
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
ini_set('display_errors', 1);
$autoloader = require dirname(__DIR__) . '/vendor/autoload.php';
$environment = App\AppFactory::buildEnvironment(
[
App\Environment::BASE_DIR => dirname(__DIR__),
]
);
$console = new Silly\Application('AzuraCast installer', App\Version::FALLBACK_VERSION);
$console->command(
'install [--defaults] [--http-port=] [--https-port=] [--release-channel=] [base-dir]',
new App\Installer\Command\InstallCommand($environment)
);
$console->setDefaultCommand('install');
$console->run();

View File

@ -76,7 +76,9 @@
"symfony/redis-messenger": "^5",
"symfony/serializer": "^5",
"symfony/validator": "^5",
"symfony/yaml": "^5.3",
"theiconic/php-ga-measurement-protocol": "^2.9",
"vlucas/phpdotenv": "^5.3",
"voku/portable-utf8": "^5.4",
"wikimedia/composer-merge-plugin": "dev-master",
"zircote/swagger-php": "^3"

241
composer.lock generated
View File

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c0d14f7166d06e2d66bff25df9a2162e",
"content-hash": "b8d01546a885d3765652fdf3d8ba59fc",
"packages": [
{
"name": "aws/aws-sdk-php",
"version": "3.185.6",
"version": "3.185.9",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "35310302912fdc3b4a0e829b84424c41e3fd9727"
"reference": "b92714fbe995195e9ba970cf52a2fa601b334725"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/35310302912fdc3b4a0e829b84424c41e3fd9727",
"reference": "35310302912fdc3b4a0e829b84424c41e3fd9727",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b92714fbe995195e9ba970cf52a2fa601b334725",
"reference": "b92714fbe995195e9ba970cf52a2fa601b334725",
"shasum": ""
},
"require": {
@ -92,9 +92,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.185.6"
"source": "https://github.com/aws/aws-sdk-php/tree/3.185.9"
},
"time": "2021-07-02T18:13:18+00:00"
"time": "2021-07-08T18:21:21+00:00"
},
{
"name": "azuracast/azuraforms",
@ -2347,6 +2347,72 @@
],
"time": "2021-01-24T20:39:09+00:00"
},
{
"name": "graham-campbell/result-type",
"version": "v1.0.1",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Result-Type.git",
"reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/7e279d2cd5d7fbb156ce46daada972355cea27bb",
"reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb",
"shasum": ""
},
"require": {
"php": "^7.0|^8.0",
"phpoption/phpoption": "^1.7.3"
},
"require-dev": {
"phpunit/phpunit": "^6.5|^7.5|^8.5|^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"GrahamCampbell\\ResultType\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "graham@alt-three.com"
}
],
"description": "An Implementation Of The Result Type",
"keywords": [
"Graham Campbell",
"GrahamCampbell",
"Result Type",
"Result-Type",
"result"
],
"support": {
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.1"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
"type": "tidelift"
}
],
"time": "2020-04-13T13:17:36+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "7.3.0",
@ -4847,6 +4913,75 @@
},
"time": "2021-06-01T14:30:21+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.7.5",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
"reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525",
"reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.7-dev"
}
},
"autoload": {
"psr-4": {
"PhpOption\\": "src/PhpOption/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Johannes M. Schmitt",
"email": "schmittjoh@gmail.com"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com"
}
],
"description": "Option Type for PHP",
"keywords": [
"language",
"option",
"php",
"type"
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
"source": "https://github.com/schmittjoh/php-option/tree/1.7.5"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
"type": "tidelift"
}
],
"time": "2020-07-20T17:29:33+00:00"
},
{
"name": "psr/cache",
"version": "1.0.1",
@ -8834,6 +8969,86 @@
},
"time": "2020-09-24T23:37:47+00:00"
},
{
"name": "vlucas/phpdotenv",
"version": "v5.3.0",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/b3eac5c7ac896e52deab4a99068e3f4ab12d9e56",
"reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56",
"shasum": ""
},
"require": {
"ext-pcre": "*",
"graham-campbell/result-type": "^1.0.1",
"php": "^7.1.3 || ^8.0",
"phpoption/phpoption": "^1.7.4",
"symfony/polyfill-ctype": "^1.17",
"symfony/polyfill-mbstring": "^1.17",
"symfony/polyfill-php80": "^1.17"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"ext-filter": "*",
"phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5.1"
},
"suggest": {
"ext-filter": "Required to use the boolean validator."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.3-dev"
}
},
"autoload": {
"psr-4": {
"Dotenv\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"homepage": "https://gjcampbell.co.uk/"
},
{
"name": "Vance Lucas",
"email": "vance@vancelucas.com",
"homepage": "https://vancelucas.com/"
}
],
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
"keywords": [
"dotenv",
"env",
"environment"
],
"support": {
"issues": "https://github.com/vlucas/phpdotenv/issues",
"source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
"type": "tidelift"
}
],
"time": "2021-01-20T15:23:13+00:00"
},
{
"name": "voku/portable-ascii",
"version": "1.5.6",
@ -10806,16 +11021,16 @@
},
{
"name": "phpstan/phpstan-doctrine",
"version": "0.12.41",
"version": "0.12.42",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-doctrine.git",
"reference": "062fc75df9a393dc38a9722e0e8ab7036f5429ae"
"reference": "e3173175dcdaf808d5ca6408528dca669e4de19f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/062fc75df9a393dc38a9722e0e8ab7036f5429ae",
"reference": "062fc75df9a393dc38a9722e0e8ab7036f5429ae",
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/e3173175dcdaf808d5ca6408528dca669e4de19f",
"reference": "e3173175dcdaf808d5ca6408528dca669e4de19f",
"shasum": ""
},
"require": {
@ -10868,9 +11083,9 @@
"description": "Doctrine extensions for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-doctrine/issues",
"source": "https://github.com/phpstan/phpstan-doctrine/tree/0.12.41"
"source": "https://github.com/phpstan/phpstan-doctrine/tree/0.12.42"
},
"time": "2021-07-04T09:30:39+00:00"
"time": "2021-07-08T08:41:15+00:00"
},
{
"name": "phpunit/php-code-coverage",

View File

@ -155,7 +155,7 @@ return [
? (string)$localeObj
: Locale::DEFAULT_LOCALE;
$locale = explode('.', $locale)[0];
$locale = explode('.', $locale, 2)[0];
$localeShort = substr($locale, 0, 2);
$localeWithDashes = str_replace('_', '-', $locale);

View File

@ -1,7 +1,7 @@
<?php
/** @var \App\Environment $environment */
$locale_select = $environment->getSupportedLocales();
$locale_select = \App\Locale::SUPPORTED_LOCALES;
$locale_select = ['default' => __('Use Browser Default')] + $locale_select;
return [

View File

@ -0,0 +1,10 @@
services :
installer :
container_name : azuracast_installer
image : 'ghcr.io/azuracast/web:${AZURACAST_VERSION:-latest}'
volumes :
- './:/installer'
restart : 'no'
user : root
entrypoint : docker_installer
command : install

127
docker.sh
View File

@ -1,9 +1,10 @@
#!/usr/bin/env bash
# shellcheck disable=SC2145,SC2178,SC2120,SC2162
set -x
# Constants
export COMPOSE_VERSION=1.29.2
export LEGACY_PORTS="8000,8005,8006,8010,8015,8016,8020,8025,8026,8030,8035,8036,8040,8045,8046,8050,8055,8056,8060,8065,8066,8070,8075,8076,8090,8095,8096,8100,8105,8106,8110,8115,8116,8120,8125,8126,8130,8135,8136,8140,8145,8146,8150,8155,8156,8160,8165,8166,8170,8175,8176,8180,8185,8186,8190,8195,8196,8200,8205,8206,8210,8215,8216,8220,8225,8226,8230,8235,8236,8240,8245,8246,8250,8255,8256,8260,8265,8266,8270,8275,8276,8280,8285,8286,8290,8295,8296,8300,8305,8306,8310,8315,8316,8320,8325,8326,8330,8335,8336,8340,8345,8346,8350,8355,8356,8360,8365,8366,8370,8375,8376,8380,8385,8386,8390,8395,8396,8400,8405,8406,8410,8415,8416,8420,8425,8426,8430,8435,8436,8440,8445,8446,8450,8455,8456,8460,8465,8466,8470,8475,8476,8480,8485,8486,8490,8495,8496"
# Functions to manage .env files
__dotenv=
@ -120,6 +121,25 @@ version-number() {
echo "$@" | awk -F. '{ printf("%03d%03d%03d\n", $1,$2,$3); }'
}
# Get the current release channel for AzuraCast
get-release-channel() {
local AZURACAST_VERSION="latest"
if [[ -f .env ]]; then
.env --file .env get AZURACAST_VERSION
AZURACAST_VERSION="${REPLY:-latest}"
fi
echo "$AZURACAST_VERSION"
}
get-release-branch-name() {
if [[ $(get-release-channel) == "stable" ]]; then
echo "stable"
else
echo "main"
fi
}
# 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.
@ -197,11 +217,6 @@ setup-letsencrypt() {
# Configure release mode settings.
#
setup-release() {
if [[ ! -f .env ]]; then
echo "Writing default .env file..."
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/main/sample.env -o .env
fi
local AZURACAST_VERSION="latest"
if ask "Prefer stable release versions of AzuraCast?" N; then
AZURACAST_VERSION="stable"
@ -243,6 +258,16 @@ install-docker-compose() {
fi
}
run-installer() {
local AZURACAST_RELEASE_BRANCH
AZURACAST_RELEASE_BRANCH=$(get-release-branch-name)
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/$AZURACAST_RELEASE_BRANCH/docker-compose.installer.yml -o docker-compose.installer.yml
docker-compose -f docker-compose.installer.yml pull
docker-compose -f docker-compose.installer.yml run --rm installer install "$@"
}
#
# Run the initial installer of Docker and AzuraCast.
# Usage: ./docker.sh install
@ -271,43 +296,16 @@ install() {
fi
fi
if [[ ! -f .env ]]; then
setup-release
fi
run-installer
if [[ ! -f azuracast.env ]]; then
echo "Creating default AzuraCast settings file..."
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/main/azuracast.sample.env -o azuracast.env
# Installer creates a file at docker-compose.new.yml; copy it to the main spot.
rm docker-compose.yml
mv docker-compose.new.yml docker-compose.yml
# Generate a random password and replace the MariaDB password with it.
local NEW_PASSWORD
NEW_PASSWORD=$(
tr </dev/urandom -dc _A-Z-a-z-0-9 | head -c"${1:-32}"
echo
)
sed -i "s/azur4c457/${NEW_PASSWORD}/g" azuracast.env
fi
if [[ ! -f docker-compose.yml ]]; then
echo "Retrieving default docker-compose.yml file..."
.env --file .env get AZURACAST_VERSION
local AZURACAST_VERSION
AZURACAST_VERSION="${REPLY:-latest}"
if [[ $AZURACAST_VERSION == "stable" ]]; then
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/stable/docker-compose.sample.yml -o docker-compose.yml
else
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/main/docker-compose.sample.yml -o docker-compose.yml
fi
fi
if ask "Customize AzuraCast ports?" N; then
setup-ports
fi
if ask "Set up LetsEncrypt?" N; then
setup-letsencrypt
# If this script is running as a non-root user, set the PUID/PGID in the environment vars appropriately.
if [[ $EUID -ne 0 ]]; then
.env --file .env set AZURACAST_PUID="$(id -u)"
.env --file .env set AZURACAST_PGID="$(id -g)"
fi
docker-compose pull
@ -325,15 +323,18 @@ update() {
if ask "Are you ready to continue with the update?" Y; then
# Check for a new Docker Utility Script.
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/main/docker.sh -o docker.new.sh
local AZURACAST_RELEASE_BRANCH
AZURACAST_RELEASE_BRANCH=$(get-release-branch-name)
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/$AZURACAST_RELEASE_BRANCH/docker.sh -o docker.new.sh
local UTILITY_FILES_MATCH
UTILITY_FILES_MATCH="$(
cmp --silent docker.sh docker.new.sh
echo $?
)"
local UPDATE_UTILITY=0
local UPDATE_UTILITY=0
if [[ ${UTILITY_FILES_MATCH} -ne 0 ]]; then
if ask "The Docker Utility Script has changed since your version. Update to latest version?" Y; then
UPDATE_UTILITY=1
@ -351,11 +352,6 @@ update() {
rm docker.new.sh
fi
if [[ ! -f azuracast.env ]]; then
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/main/azuracast.sample.env -o azuracast.env
echo "Default environment file loaded."
fi
# Check for update to Docker Compose
local CURRENT_COMPOSE_VERSION
CURRENT_COMPOSE_VERSION=$(docker-compose version --short)
@ -366,28 +362,7 @@ update() {
fi
fi
# Migrate previous release settings to new environment variable.
.env --file azuracast.env get PREFER_RELEASE_BUILDS
local PREFER_RELEASE_BUILDS
PREFER_RELEASE_BUILDS="${REPLY:-false}"
if [[ $PREFER_RELEASE_BUILDS == "true" ]]; then
.env --file .env set AZURACAST_VERSION=stable
fi
.env --file azuracast.env set PREFER_RELEASE_BUILDS
# Check for new Docker Compose file
.env --file .env get AZURACAST_VERSION
local AZURACAST_VERSION
AZURACAST_VERSION="${REPLY:-latest}"
if [[ $AZURACAST_VERSION == "stable" ]]; then
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/stable/docker-compose.sample.yml -o docker-compose.new.yml
else
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/main/docker-compose.sample.yml -o docker-compose.new.yml
fi
run-installer
# Check for updated Docker Compose config.
local COMPOSE_FILES_MATCH
@ -395,15 +370,8 @@ update() {
cmp --silent docker-compose.yml docker-compose.new.yml
echo $?
)"
local UPDATE_COMPOSE=0
if [[ ${COMPOSE_FILES_MATCH} -ne 0 ]]; then
if ask "The docker-compose.yml file has changed since your version. Overwrite? This will overwrite any customizations you made to this file?" Y; then
UPDATE_COMPOSE=1
fi
fi
if [[ ${UPDATE_COMPOSE} -ne 0 ]]; then
docker-compose -f docker-compose.new.yml pull
docker-compose down
@ -437,7 +405,10 @@ update() {
# Usage: ./docker.sh update-self
#
update-self() {
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/main/docker.sh -o docker.sh
local AZURACAST_RELEASE_BRANCH
AZURACAST_RELEASE_BRANCH=$(get-release-branch-name)
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/$AZURACAST_RELEASE_BRANCH/docker.sh -o docker.sh
chmod a+x docker.sh
echo "New Docker utility script downloaded."

View File

@ -27,6 +27,7 @@
warnings would make the code partly less readable
-->
<exclude-pattern>src/Radio/Backend/Liquidsoap/ConfigWriter.php</exclude-pattern>
<exclude-pattern>src/Installer/EnvFiles/*.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Arrays.TrailingArrayComma"/>

View File

@ -37,7 +37,8 @@ class AppFactory
$di = self::buildContainer($autoloader, $appEnvironment, $diDefinitions);
self::buildAppFromContainer($di);
$locale = $di->make(Locale::class);
$env = $di->get(Environment::class);
$locale = Locale::createForCli($env);
$locale->register();
return $di->get(Application::class);
@ -164,7 +165,7 @@ class AppFactory
return $di;
}
protected static function buildEnvironment(array $environment): Environment
public static function buildEnvironment(array $environment): Environment
{
if (!isset($environment[Environment::BASE_DIR])) {
throw new Exception\BootstrapException('No base directory specified!');

View File

@ -4,6 +4,7 @@ namespace App\Console\Command\Locale;
use App\Console\Command\CommandAbstract;
use App\Environment;
use App\Locale;
use Gettext\Translations;
use Symfony\Component\Console\Style\SymfonyStyle;
@ -15,7 +16,7 @@ class ImportCommand extends CommandAbstract
): int {
$io->title('Import Locales');
$locales = $environment->getSupportedLocales();
$locales = Locale::SUPPORTED_LOCALES;
$locale_base = $environment->getBaseDirectory() . '/resources/locale';
foreach ($locales as $locale_key => $locale_name) {

View File

@ -53,7 +53,7 @@ class Customization
}
// Register locale
$this->locale = new Locale($environment, $request);
$this->locale = Locale::createFromRequest($this->environment, $request);
$this->locale->register();
}

View File

@ -34,7 +34,6 @@ class Environment
public const DOCKER_REVISION = 'AZURACAST_DC_REVISION';
public const LANG = 'LANG';
public const SUPPORTED_LOCALES = 'SUPPORTED_LOCALES';
public const RELEASE_CHANNEL = 'AZURACAST_VERSION';
@ -69,32 +68,34 @@ class Environment
self::APP_NAME => 'AzuraCast',
self::APP_ENV => self::ENV_PRODUCTION,
self::LOG_LEVEL => LogLevel::NOTICE,
self::IS_DOCKER => true,
self::IS_CLI => ('cli' === PHP_SAPI),
self::ASSET_URL => '/static',
self::ENABLE_REDIS => true,
self::AUTO_ASSIGN_PORT_MIN => 8000,
self::AUTO_ASSIGN_PORT_MAX => 8499,
self::SUPPORTED_LOCALES => [
'en_US.UTF-8' => 'English (Default)',
'cs_CZ.UTF-8' => 'čeština', // Czech
'de_DE.UTF-8' => 'Deutsch', // German
'es_ES.UTF-8' => 'Español', // Spanish
'fr_FR.UTF-8' => 'Français', // French
'el_GR.UTF-8' => 'ελληνικά', // Greek
'it_IT.UTF-8' => 'Italiano', // Italian
'hu_HU.UTF-8' => 'magyar', // Hungarian
'nl_NL.UTF-8' => 'Nederlands', // Dutch
'pl_PL.UTF-8' => 'Polski', // Polish
'pt_PT.UTF-8' => 'Português', // Portuguese
'pt_BR.UTF-8' => 'Português do Brasil', // Brazilian Portuguese
'ru_RU.UTF-8' => 'Русский язык', // Russian
'sv_SE.UTF-8' => 'Svenska', // Swedish
'tr_TR.UTF-8' => 'Türkçe', // Turkish
'zh_CN.UTF-8' => '簡化字', // Simplified Chinese
'ko_KR.UTF-8' => '한국어', // Korean (South Korean)
],
self::DB_HOST => 'mariadb',
self::DB_PORT => 3306,
self::DB_USER => 'azuracast',
self::DB_PASSWORD => 'azur4c457',
self::DB_NAME => 'azuracast',
self::ENABLE_REDIS => true,
self::REDIS_HOST => 'redis',
self::REDIS_PORT => 6379,
self::REDIS_DB => 1,
self::SYNC_SHORT_EXECUTION_TIME => 600,
self::SYNC_LONG_EXECUTION_TIME => 1800,
self::PROFILING_EXTENSION_ENABLED => 0,
self::PROFILING_EXTENSION_ALWAYS_ON => 0,
self::PROFILING_EXTENSION_HTTP_KEY => 'dev',
self::LANG => Locale::DEFAULT_LOCALE,
];
public function __construct(array $elements = [])
@ -102,6 +103,14 @@ class Environment
$this->data = array_merge($this->defaults, $elements);
}
/**
* @return mixed[]
*/
public function toArray(): array
{
return $this->data;
}
protected function envToBool(string|bool $value): bool
{
if (is_bool($value)) {
@ -216,14 +225,6 @@ class Environment
return $this->data[self::LANG];
}
/**
* @return string[]
*/
public function getSupportedLocales(): array
{
return $this->data[self::SUPPORTED_LOCALES] ?? [];
}
public function getReleaseChannel(): string
{
$channel = $this->data[self::RELEASE_CHANNEL] ?? 'latest';

View File

@ -0,0 +1,255 @@
<?php
namespace App\Installer\Command;
use App\Environment;
use App\Installer\EnvFiles\AzuraCastEnvFile;
use App\Installer\EnvFiles\EnvFile;
use App\Locale;
use App\Radio\Configuration;
use App\Utilities\Strings;
use InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Yaml\Yaml;
class InstallCommand
{
public const DEFAULT_BASE_DIRECTORY = '/installer';
public function __construct(
protected Environment $environment
) {
}
public function __invoke(
SymfonyStyle $io,
OutputInterface $output,
bool $defaults,
?int $httpPort = null,
?int $httpsPort = null,
?string $releaseChannel = null,
?string $baseDir = self::DEFAULT_BASE_DIRECTORY
): int {
// Initialize all the environment variables.
$envPath = EnvFile::buildPathFromBase($baseDir);
$azuracastEnvPath = AzuraCastEnvFile::buildPathFromBase($baseDir);
$isNewInstall = !is_file($envPath);
try {
$env = EnvFile::fromEnvFile($envPath);
} catch (InvalidArgumentException $e) {
$io->error($e->getMessage());
$env = new EnvFile($envPath);
}
try {
$azuracastEnv = AzuraCastEnvFile::fromEnvFile($azuracastEnvPath);
} catch (InvalidArgumentException $e) {
$io->error($e->getMessage());
$azuracastEnv = new AzuraCastEnvFile($envPath);
}
// Initialize locale for translated installer/updater.
if ($isNewInstall || empty($azuracastEnv[Environment::LANG])) {
$langOptions = [];
foreach (Locale::SUPPORTED_LOCALES as $langKey => $langName) {
$langOptions[Locale::stripLocaleEncoding($langKey)] = $langName;
}
$azuracastEnv[Environment::LANG] = $io->choice(
'Select Language',
$langOptions,
Locale::stripLocaleEncoding(Locale::DEFAULT_LOCALE)
);
}
$locale = new Locale($this->environment, $azuracastEnv[Environment::LANG] ?? Locale::DEFAULT_LOCALE);
$locale->register();
$envConfig = EnvFile::getConfiguration();
$env->setFromDefaults();
$azuracastEnvConfig = AzuraCastEnvFile::getConfiguration();
$azuracastEnv->setFromDefaults();
// Apply values passed via flags
if (null !== $releaseChannel) {
$env['AZURACAST_VERSION'] = $releaseChannel;
}
if (null !== $httpPort) {
$env['AZURACAST_HTTP_PORT'] = $httpPort;
}
if (null !== $httpsPort) {
$env['AZURACAST_HTTPS_PORT'] = $httpsPort;
}
// Migrate legacy config values.
if (isset($azuracastEnv['PREFER_RELEASE_BUILDS'])) {
$env['AZURACAST_VERSION'] = ('true' === $azuracastEnv['PREFER_RELEASE_BUILDS'])
? 'stable'
: 'latest';
unset($azuracastEnv['PREFER_RELEASE_BUILDS']);
}
unset($azuracastEnv['ENABLE_ADVANCED_FEATURES']);
// Randomize the MariaDB root password for new installs.
if ($isNewInstall && 'azur4c457' === $azuracastEnv[Environment::DB_PASSWORD]) {
$azuracastEnv[Environment::DB_PASSWORD] = Strings::generatePassword(12);
}
// Display header messages
if ($isNewInstall) {
$io->title(
__('AzuraCast Installer')
);
$io->block(
__('Welcome to AzuraCast! Complete the initial server setup by answering a few questions.')
);
} else {
$io->title(
__('AzuraCast Updater')
);
}
if ($defaults) {
$customize = false;
} else {
$customize = $io->confirm(
__('Customize server settings (ports, databases, etc.)?'),
false
);
}
if ($customize) {
// Release channel
$env['AZURACAST_VERSION'] = $io->choice(
__('AzuraCast Release Channel'),
[
'stable' => __('Stable'),
'latest' => __('Rolling Release'),
],
$env['AZURACAST_VERSION']
);
// Port customization
$io->writeln(
__('AzuraCast is currently configured to listen on the following ports:'),
);
$io->listing(
[
__('HTTP Port: %d', $env['AZURACAST_HTTP_PORT']),
__('HTTPS Port: %d', $env['AZURACAST_HTTPS_PORT']),
__('SFTP Port: %d', $env['AZURACAST_SFTP_PORT']),
__('Radio Ports: %s', $env['AZURACAST_STATION_PORTS']),
],
);
$customizePorts = $io->confirm(
__('Customize ports used for AzuraCast?'),
false
);
if ($customizePorts) {
$simplePorts = [
'AZURACAST_HTTP_PORT',
'AZURACAST_HTTPS_PORT',
'AZURACAST_SFTP_PORT',
];
foreach ($simplePorts as $port) {
$env[$port] = (int)$io->ask(
$envConfig[$port]['name'] . ' - ' . $envConfig[$port]['description'],
(int)$env[$port]
);
}
$azuracastEnv[Environment::AUTO_ASSIGN_PORT_MIN] = (int)$io->ask(
$azuracastEnvConfig[Environment::AUTO_ASSIGN_PORT_MIN]['name'],
(int)$azuracastEnv[Environment::AUTO_ASSIGN_PORT_MIN]
);
$azuracastEnv[Environment::AUTO_ASSIGN_PORT_MAX] = (int)$io->ask(
$azuracastEnvConfig[Environment::AUTO_ASSIGN_PORT_MAX]['name'],
(int)$azuracastEnv[Environment::AUTO_ASSIGN_PORT_MAX]
);
$stationPorts = Configuration::enumerateDefaultPorts(
rangeMin: $azuracastEnv[Environment::AUTO_ASSIGN_PORT_MIN],
rangeMax: $azuracastEnv[Environment::AUTO_ASSIGN_PORT_MAX]
);
$env['AZURACAST_STATION_PORTS'] = implode(',', $stationPorts);
}
$customizeLetsEncrypt = $io->confirm(
__('Set up LetsEncrypt?'),
false
);
if ($customizeLetsEncrypt) {
$env['LETSENCRYPT_HOST'] = $io->ask(
$envConfig['LETSENCRYPT_HOST']['description'],
$env['LETSENCRYPT_HOST']
);
$env['LETSENCRYPT_EMAIL'] = $io->ask(
$envConfig['LETSENCRYPT_EMAIL']['description'],
$env['LETSENCRYPT_EMAIL']
);
}
}
$io->writeln(
__('Writing configuration files...')
);
$envStr = $env->writeToFile();
$azuracastEnvStr = $azuracastEnv->writeToFile();
if ($io->isVerbose()) {
$io->section($env->getBasename());
$io->block($envStr);
$io->section($azuracastEnv->getBasename());
$io->block($azuracastEnvStr);
}
$dockerComposePath = $baseDir . '/docker-compose.new.yml';
$dockerComposeStr = $this->updateDockerCompose($dockerComposePath, $env['AZURACAST_STATION_PORTS']);
if ($io->isVerbose()) {
$io->section(basename($dockerComposePath));
$io->block($dockerComposeStr);
}
$io->success(
__('Server configuration complete!')
);
return 0;
}
protected function updateDockerCompose(
string $dockerComposePath,
string $ports
): string {
// Parse port listing and convert into YAML format.
$yamlPorts = [];
foreach (explode(',', $ports) as $port) {
$yamlPorts[] = $port . ':' . $port;
}
// Attempt to parse Docker Compose YAML file
$sampleFile = $this->environment->getBaseDirectory() . '/docker-compose.sample.yml';
$yaml = Yaml::parseFile($sampleFile);
$yaml['services']['stations']['ports'] = $yamlPorts;
$yamlRaw = Yaml::dump($yaml, PHP_INT_MAX);
file_put_contents($dockerComposePath, $yamlRaw);
return $yamlRaw;
}
}

View File

@ -0,0 +1,185 @@
<?php
namespace App\Installer\EnvFiles;
use App\Environment;
use App\Utilities\Strings;
use Dotenv\Dotenv;
use Dotenv\Exception\ExceptionInterface;
use http\Exception\InvalidArgumentException;
abstract class AbstractEnvFile implements \ArrayAccess
{
final public function __construct(
protected string $path,
protected array $data = []
) {
}
public function getPath(): string
{
return $this->path;
}
public function getBasename(): string
{
return basename($this->path);
}
public function setFromDefaults(): void
{
$currentVars = array_filter($this->data);
$defaults = [];
foreach (static::getConfiguration() as $key => $keyInfo) {
if (isset($keyInfo['default'])) {
$defaults[$key] = $keyInfo['default'] ?? null;
}
}
$this->data = array_merge($defaults, $currentVars);
}
public function offsetExists($offset): bool
{
return isset($this->data[$offset]);
}
public function offsetGet($offset): mixed
{
return $this->data[$offset];
}
public function offsetSet($offset, $value): void
{
$this->data[$offset] = $value;
}
public function offsetUnset($offset): void
{
unset($this->data[$offset]);
}
public function writeToFile(): string
{
$values = array_filter($this->data);
$envFile = [
'# ' . __('This file was automatically generated by AzuraCast.'),
'# ' . __('You can modify it as necessary. To apply changes, restart the Docker containers.'),
'# ' . __('Remove the leading "#" symbol from lines to uncomment them.'),
'',
];
foreach (static::getConfiguration() as $key => $keyInfo) {
$envFile[] = '# ' . ($keyInfo['name'] ?? $key);
if (!empty($keyInfo['description'])) {
$desc = Strings::mbWordwrap($keyInfo['description']);
foreach (explode("\n", $desc) as $descPart) {
$envFile[] = '# ' . $descPart;
}
}
if (!empty($keyInfo['options'])) {
$options = array_map(
fn($val) => $this->getEnvValue($val),
$keyInfo['options'],
);
$envFile[] = '# ' . __('Valid options: %s', implode(', ', $options));
}
if (isset($values[$key])) {
$value = $this->getEnvValue($values[$key]);
unset($values[$key]);
} else {
$value = null;
}
if (!empty($keyInfo['default'])) {
$default = $this->getEnvValue($keyInfo['default']);
$envFile[] = '# ' . __('Default: %s', $default);
} else {
$default = '';
}
if ((null === $value || $default === $value) && Environment::LANG !== $key) {
$value ??= $default;
$envFile[] = '# ' . $key . '=' . $value;
} else {
$envFile[] = $key . '=' . $value;
}
$envFile[] = '';
}
// Add in other environment vars that were missed or previously present.
if (!empty($values)) {
$envFile[] = '# ' . __('Additional Environment Variables');
foreach ($values as $key => $value) {
$envFile[] = $key . '=' . $this->getEnvValue($value);
}
}
$envFileStr = implode("\n", $envFile);
file_put_contents($this->path, $envFileStr);
return $envFileStr;
}
protected function getEnvValue(
mixed $value
): string {
if (is_null($value)) {
return '';
}
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
if (is_int($value)) {
return (string)$value;
}
if (is_array($value)) {
return implode(',', $value);
}
if (str_contains($value, ' ')) {
$value = '"' . $value . '"';
}
return $value;
}
/**
* @return mixed[]
*/
abstract public static function getConfiguration(): array;
abstract public static function buildPathFromBase(string $baseDir): string;
public static function fromEnvFile(string $path): static
{
$data = [];
if (is_file($path)) {
$fileContents = file_get_contents($path);
if (!empty($fileContents)) {
try {
$data = Dotenv::parse($fileContents);
} catch (ExceptionInterface $e) {
throw new InvalidArgumentException(
sprintf(
'Encountered an error parsing %s: "%s". Resetting to default configuration.',
basename($path),
$e->getMessage()
)
);
}
}
}
return new static($path, $data);
}
}

View File

@ -0,0 +1,216 @@
<?php
namespace App\Installer\EnvFiles;
use App\Environment;
use App\Locale;
use Psr\Log\LogLevel;
use function __;
class AzuraCastEnvFile extends AbstractEnvFile
{
/** @inheritDoc */
public static function getConfiguration(): array
{
$defaults = (new Environment([]))->toArray();
$langOptions = [];
foreach (Locale::SUPPORTED_LOCALES as $locale => $localeName) {
$langOptions[] = Locale::stripLocaleEncoding($locale);
}
$config = [
Environment::LANG => [
'name' => __(
'The locale to use for CLI commands.',
),
'options' => $langOptions,
'default' => Locale::stripLocaleEncoding(Locale::DEFAULT_LOCALE),
],
Environment::APP_ENV => [
'name' => __(
'The application environment.',
),
'options' => [
Environment::ENV_PRODUCTION,
Environment::ENV_DEVELOPMENT,
Environment::ENV_TESTING,
],
],
Environment::LOG_LEVEL => [
'name' => __(
'Manually modify the logging level.',
),
'description' => __(
'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.'
),
'options' => [
LogLevel::DEBUG,
LogLevel::INFO,
LogLevel::NOTICE,
LogLevel::WARNING,
LogLevel::ERROR,
LogLevel::CRITICAL,
LogLevel::ALERT,
LogLevel::EMERGENCY,
],
],
'COMPOSER_PLUGIN_MODE' => [
'name' => __('Composer Plugin Mode'),
'description' => __(
'Enable the composer "merge" functionality to combine the main application\'s composer.json file with any plugin 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.',
),
'options' => [true, false],
'default' => false,
],
Environment::AUTO_ASSIGN_PORT_MIN => [
'name' => __(
'Minimum Port for Station Port Assignment'
),
'description' => __(
'Modify this if your stations are listening on nonstandard ports.',
),
],
Environment::AUTO_ASSIGN_PORT_MAX => [
'name' => __(
'Maximum Port for Station Port Assignment'
),
'description' => __(
'Modify this if your stations are listening on nonstandard ports.',
),
],
Environment::DB_HOST => [
'name' => __('MariaDB Host'),
'description' => __(
'Do not modify this after installation.',
),
],
Environment::DB_PORT => [
'name' => __('MariaDB Port'),
'description' => __(
'Do not modify this after installation.',
),
],
Environment::DB_USER => [
'name' => __('MariaDB Username'),
'description' => __(
'Do not modify this after installation.',
),
],
Environment::DB_PASSWORD => [
'name' => __('MariaDB Password'),
'description' => __(
'Do not modify this after installation.',
),
],
Environment::DB_NAME => [
'name' => __('MariaDB Database Name'),
'description' => __(
'Do not modify this after installation.',
),
],
'MYSQL_RANDOM_ROOT_PASSWORD' => [
'name' => __('Auto-generate Random MariaDB Root Password'),
'description' => __(
'Do not modify this after installation.',
),
'default' => 'yes',
],
'MYSQL_SLOW_QUERY_LOG' => [
'name' => __('Enable MariaDB Slow Query Log'),
'description' => __(
'Log slower queries to diagnose possible database issues. Only turn this on if needed.',
),
'default' => 0,
],
'MYSQL_MAX_CONNECTIONS' => [
'name' => __('MariaDB Maximum Connections'),
'description' => __(
'Set the amount of allowed connections to the database. This value should be increased if you are seeing the "Too many connections" error in the logs.',
),
'default' => 100,
],
Environment::ENABLE_REDIS => [
'name' => __('Enable Redis'),
'description' => __(
'Disable to use a flatfile cache instead of Redis.',
),
],
Environment::REDIS_HOST => [
'name' => __('Redis Host'),
],
Environment::REDIS_PORT => [
'name' => __('Redis Port'),
],
Environment::REDIS_DB => [
'name' => __('Redis Database Index'),
'options' => range(0, 15),
],
'PHP_MAX_FILE_SIZE' => [
'name' => __('PHP Maximum POST File Size'),
'default' => '25M',
],
'PHP_MEMORY_LIMIT' => [
'name' => __('PHP Memory Limit'),
'default' => '128M',
],
'PHP_MAX_EXECUTION_TIME' => [
'name' => __('PHP Script Maximum Execution Time'),
'description' => __('(in seconds)'),
'default' => 30,
],
Environment::SYNC_SHORT_EXECUTION_TIME => [
'name' => __('Short Sync Task Execution Time'),
'description' => __(
'The maximum execution time (and lock timeout) for the 15-second, 1-minute and 5-minute synchronization tasks.'
),
],
Environment::SYNC_LONG_EXECUTION_TIME => [
'name' => __('Long Sync Task Execution Time'),
'description' => __(
'The maximum execution time (and lock timeout) for the 1-hour synchronization task.',
),
],
'PHP_FPM_MAX_CHILDREN' => [
'name' => __('Maximum PHP-FPM Worker Processes'),
'default' => 5,
],
Environment::PROFILING_EXTENSION_ENABLED => [
'name' => __('Enable Performance Profiling Extension'),
'description' => __(
'Profiling data can be viewed by visiting %s.',
'http://your-azuracast-site/?SPX_KEY=dev&SPX_UI_URI=/',
),
],
Environment::PROFILING_EXTENSION_ALWAYS_ON => [
'name' => __('Profile Performance on All Requests'),
'description' => __(
'This will have a significant performance impact on your installation.',
),
],
Environment::PROFILING_EXTENSION_HTTP_KEY => [
'name' => __('Profiling Extension HTTP Key'),
'description' => __(
'The value for the "SPX_KEY" parameter for viewing profiling pages.',
),
],
'PROFILING_EXTENSION_HTTP_IP_WHITELIST' => [
'name' => __('Profiling Extension IP Allow List'),
'options' => ['127.0.0.1', '*'],
'default' => '127.0.0.1',
],
];
foreach ($config as $key => &$keyInfo) {
$keyInfo['default'] ??= $defaults[$key] ?? null;
}
return $config;
}
public static function buildPathFromBase(string $baseDir): string
{
return $baseDir . DIRECTORY_SEPARATOR . 'azuracast.env';
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace App\Installer\EnvFiles;
use App\Radio\Configuration;
use function __;
class EnvFile extends AbstractEnvFile
{
/** @inheritDoc */
public static function getConfiguration(): array
{
return [
'COMPOSE_PROJECT_NAME' => [
'name' => __(
'(Docker Compose) All Docker containers are prefixed by this name. Do not change this after installation.'
),
'default' => 'azuracast',
],
'COMPOSE_HTTP_TIMEOUT' => [
'name' => __(
'(Docker Compose) The amount of time to wait before a Docker Compose operation fails. Increase this on lower performance computers.'
),
'default' => 300,
],
'AZURACAST_VERSION' => [
'name' => __('AzuraCast Release Channel'),
'options' => ['latest', 'stable'],
'default' => 'latest',
],
'AZURACAST_HTTP_PORT' => [
'name' => __('HTTP Port'),
'description' => __(
'The main port AzuraCast listens to for insecure HTTP connections.',
),
'default' => 80,
],
'AZURACAST_HTTPS_PORT' => [
'name' => __('HTTPS Port'),
'description' => __(
'The main port AzuraCast listens to for secure HTTPS connections.',
),
'default' => 443,
],
'AZURACAST_SFTP_PORT' => [
'name' => __('SFTP Port'),
'description' => __(
'The port AzuraCast listens to for SFTP file management connections.',
),
'default' => 2022,
],
'AZURACAST_STATION_PORTS' => [
'name' => __('Station Ports'),
'description' => __(
'The ports AzuraCast should listen to for station broadcasts and incoming DJ connections.',
),
'default' => implode(',', Configuration::enumerateDefaultPorts()),
],
'AZURACAST_PUID' => [
'name' => __('Docker User UID'),
'description' => __(
'Set the UID of the user running inside the Docker containers. Matching this with your host UID can fix permission issues.',
),
'default' => 1000,
],
'AZURACAST_PGID' => [
'name' => __('Docker User GID'),
'description' => __(
'Set the GID of the user running inside the Docker containers. Matching this with your host GID can fix permission issues.'
),
'default' => 1000,
],
'LETSENCRYPT_HOST' => [
'name' => __('LetsEncrypt Domain Name(s)'),
'description' => __(
'Domain name (example.com) or names (example.com,foo.bar) to use with LetsEncrypt.'
),
],
'LETSENCRYPT_EMAIL' => [
'name' => __('LetsEncrypt E-mail Address'),
'description' => __(
'Optionally provide an e-mail address for updates from LetsEncrypt.',
),
],
];
}
public static function buildPathFromBase(string $baseDir): string
{
return $baseDir . DIRECTORY_SEPARATOR . '.env';
}
}

View File

@ -10,53 +10,46 @@ class Locale
{
public const DEFAULT_LOCALE = 'en_US.UTF-8';
public const SUPPORTED_LOCALES = [
'en_US.UTF-8' => 'English (Default)',
'cs_CZ.UTF-8' => 'čeština', // Czech
'de_DE.UTF-8' => 'Deutsch', // German
'es_ES.UTF-8' => 'Español', // Spanish
'fr_FR.UTF-8' => 'Français', // French
'el_GR.UTF-8' => 'ελληνικά', // Greek
'it_IT.UTF-8' => 'Italiano', // Italian
'hu_HU.UTF-8' => 'magyar', // Hungarian
'nl_NL.UTF-8' => 'Nederlands', // Dutch
'pl_PL.UTF-8' => 'Polski', // Polish
'pt_PT.UTF-8' => 'Português', // Portuguese
'pt_BR.UTF-8' => 'Português do Brasil', // Brazilian Portuguese
'ru_RU.UTF-8' => 'Русский язык', // Russian
'sv_SE.UTF-8' => 'Svenska', // Swedish
'tr_TR.UTF-8' => 'Türkçe', // Turkish
'zh_CN.UTF-8' => '簡化字', // Simplified Chinese
'ko_KR.UTF-8' => '한국어', // Korean (South Korean)
];
protected string $locale = self::DEFAULT_LOCALE;
public function __construct(
protected Environment $environment,
protected ?ServerRequestInterface $request = null
string|array $possibleLocales
) {
$this->locale = $this->determineLocale();
}
protected function determineLocale(): string
{
$possibleLocales = [];
// Attempt to load from request if provided.
if ($this->request instanceof ServerRequestInterface) {
// Prefer user-based profile locale.
$user = $this->request->getAttribute(ServerRequest::ATTR_USER);
if (null !== $user && !empty($user->getLocale()) && 'default' !== $user->getLocale()) {
$possibleLocales[] = $user->getLocale();
}
$server_params = $this->request->getServerParams();
$browser_locale = \Locale::acceptFromHttp($server_params['HTTP_ACCEPT_LANGUAGE'] ?? null);
if (!empty($browser_locale)) {
if (2 === strlen($browser_locale)) {
$browser_locale = strtolower($browser_locale) . '_' . strtoupper($browser_locale);
}
$possibleLocales[] = substr($browser_locale, 0, 5) . '.UTF-8';
}
if (is_string($possibleLocales)) {
$possibleLocales = [$possibleLocales];
}
// Attempt to load from environment variable.
$envLocale = $this->environment->getLang();
if (!empty($envLocale)) {
$possibleLocales[] = substr($envLocale, 0, 5) . '.UTF-8';
}
return $this->getValidLocale($possibleLocales);
$this->locale = $this->getValidLocale($possibleLocales);
}
protected function getValidLocale(array $possibleLocales): string
{
$supportedLocales = $this->environment->getSupportedLocales();
$supportedLocales = self::SUPPORTED_LOCALES;
foreach ($possibleLocales as $locale) {
$locale = self::ensureLocaleEncoding($locale);
// Prefer exact match.
if (isset($supportedLocales[$locale])) {
return $locale;
@ -79,11 +72,11 @@ class Locale
}
/**
* @return string A shortened locale (minus .UTF-8) for use in Vue.
* @return string A shortened locale (minus .UTF-8).
*/
public function getVueLocale(): string
public function getLocaleWithoutEncoding(): string
{
return json_encode(substr($this->locale, 0, 5), JSON_THROW_ON_ERROR);
return self::stripLocaleEncoding($this->locale);
}
public function setLocale(string $newLocale = self::DEFAULT_LOCALE): void
@ -91,16 +84,23 @@ class Locale
$this->locale = $newLocale;
}
public function register(): void
public function createTranslator(): Translator
{
$translator = new Translator();
$localeBase = $this->environment->getBaseDirectory() . '/resources/locale/compiled';
$localePath = $localeBase . '/' . $this->locale . '.php';
if (file_exists($localePath)) {
$translator->loadTranslations($localePath);
}
return $translator;
}
public function register(): void
{
$translator = $this->createTranslator();
$translator->register();
// Register translation superglobal functions
@ -111,4 +111,55 @@ class Locale
{
return $this->locale;
}
public static function createFromRequest(
Environment $environment,
ServerRequestInterface $request
): self {
$possibleLocales = [];
// Prefer user-based profile locale.
$user = $request->getAttribute(ServerRequest::ATTR_USER);
if (null !== $user && !empty($user->getLocale()) && 'default' !== $user->getLocale()) {
$possibleLocales[] = $user->getLocale();
}
$server_params = $request->getServerParams();
$browser_locale = \Locale::acceptFromHttp($server_params['HTTP_ACCEPT_LANGUAGE'] ?? null);
if (!empty($browser_locale)) {
if (2 === strlen($browser_locale)) {
$browser_locale = strtolower($browser_locale) . '_' . strtoupper($browser_locale);
}
$possibleLocales[] = substr($browser_locale, 0, 5) . '.UTF-8';
}
// Attempt to load from environment variable.
$possibleLocales[] = $environment->getLang();
return new self($environment, $possibleLocales);
}
public static function createForCli(
Environment $environment
): self {
return new self(
$environment,
$environment->getLang()
);
}
public static function stripLocaleEncoding(string $locale): string
{
if (str_contains($locale, '.')) {
return explode('.', $locale, 2)[0];
}
return $locale;
}
public static function ensureLocaleEncoding(string $locale): string
{
return self::stripLocaleEncoding($locale) . '.UTF-8';
}
}

View File

@ -15,6 +15,7 @@ class Configuration
{
public const DEFAULT_PORT_MIN = 8000;
public const DEFAULT_PORT_MAX = 8499;
public const PROTECTED_PORTS = [8080, 80, 443, 2022];
public function __construct(
protected EntityManagerInterface $em,
@ -295,7 +296,7 @@ class Configuration
$used_ports = $this->getUsedPorts($station);
// Iterate from port 8000 to 9000, in increments of 10
$protected_ports = [8080];
$protected_ports = self::PROTECTED_PORTS;
$port_min = $this->environment->getAutoAssignPortMin();
$port_max = $this->environment->getAutoAssignPortMax();
@ -437,4 +438,26 @@ class Configuration
$this->reloadSupervisor();
}
/**
* @return int[]
*/
public static function enumerateDefaultPorts(
int $rangeMin = self::DEFAULT_PORT_MIN,
int $rangeMax = self::DEFAULT_PORT_MAX,
): array {
$defaultPorts = [];
for ($i = $rangeMin; $i < $rangeMax; $i += 10) {
if (in_array($i, self::PROTECTED_PORTS, true)) {
continue;
}
$defaultPorts[] = $i;
$defaultPorts[] = $i + 5;
$defaultPorts[] = $i + 6;
}
return $defaultPorts;
}
}

View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
cd /var/azuracast/www
php bin/installer "$@"