AzuraCast in a Single Docker Image (#5167)

This commit is contained in:
Buster "Silver Eagle" Neece 2022-03-08 17:00:03 -06:00 committed by GitHub
parent d1ee9db407
commit 27260729bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 1600 additions and 323 deletions

View File

@ -45,7 +45,7 @@ jobs:
uses: actions/cache@v2
with:
path: vendor
key: ${{ runner.OS }}-build-${{ hashFiles('**/composer.lock') }}
key: ${{ runner.OS }}-build-${{ hashFiles('composer.lock') }}
- name: Set console permissions and clear static assets.
run: |
@ -117,8 +117,8 @@ jobs:
with:
context: .
load: true
tags: ghcr.io/azuracast/web:latest
cache-from: type=registry,ref=ghcr.io/azuracast/web:buildcache
tags: ghcr.io/azuracast/azuracast:latest
cache-from: type=registry,ref=ghcr.io/azuracast/azuracast:buildcache
- name: Set up functional test environment.
run: |
@ -126,13 +126,12 @@ jobs:
cp azuracast.sample.env azuracast.env
cp docker-compose.sample.yml docker-compose.yml
cp docker-compose.testing.yml docker-compose.override.yml
docker-compose run --rm --user="azuracast" web azuracast_install
- name: Run functional test suite.
run: |
chmod 777 tests/_output/
chmod 777 tests/_support/_generated
docker-compose run --rm --user="azuracast" web composer codeception-no-coverage
docker-compose run --rm web azuracast_ci
- name: Stop all running containers.
run: |
@ -202,8 +201,8 @@ jobs:
uses: docker/metadata-action@v3
with:
images: |
azuracast/azuracast_web_v2
ghcr.io/azuracast/web
azuracast/azuracast
ghcr.io/azuracast/azuracast
tags: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=branch
@ -217,5 +216,5 @@ jobs:
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=ghcr.io/azuracast/web:buildcache
cache-to: type=registry,ref=ghcr.io/azuracast/web:buildcache,mode=max
cache-from: type=registry,ref=ghcr.io/azuracast/azuracast:buildcache
cache-to: type=registry,ref=ghcr.io/azuracast/azuracast:buildcache,mode=max

View File

@ -1,3 +1,11 @@
#
# Icecast build stage (for later copy)
#
FROM ghcr.io/azuracast/icecast-kh-ac:2.4.0-kh15-ac2 AS icecast
#
# Golang dependencies build step
#
FROM golang:1.17-buster AS dockerize
RUN apt-get update \
@ -5,22 +13,49 @@ RUN apt-get update \
RUN go install github.com/jwilder/dockerize@latest
#
# Final build image
FROM ubuntu:focal
#
FROM mariadb:10.5-focal
ENV TZ="UTC"
# Add Dockerize
COPY --from=dockerize /go/bin/dockerize /usr/local/bin
# Run base build process
COPY ./util/docker/web /bd_build/
# Import Icecast-KH from build container
COPY --from=icecast /usr/local/bin/icecast /usr/local/bin/icecast
COPY --from=icecast /usr/local/share/icecast /usr/local/share/icecast
# Run base build process
COPY ./util/docker/common /bd_build/
RUN chmod a+x /bd_build/*.sh \
&& /bd_build/prepare.sh \
&& /bd_build/add_user.sh \
&& /bd_build/setup.sh \
&& /bd_build/cleanup.sh \
&& /bd_build/cleanup.sh
# Build each set of dependencies in their own step for cacheability.
COPY ./util/docker/stations /bd_build/stations/
RUN bash /bd_build/stations/setup.sh \
&& bash /bd_build/cleanup.sh \
&& rm -rf /bd_build/stations
COPY ./util/docker/web /bd_build/web/
RUN bash /bd_build/web/setup.sh \
&& bash /bd_build/cleanup.sh \
&& rm -rf /bd_build/web
COPY ./util/docker/mariadb /bd_build/mariadb/
RUN bash /bd_build/mariadb/setup.sh \
&& bash /bd_build/cleanup.sh \
&& rm -rf /bd_build/mariadb
COPY ./util/docker/redis /bd_build/redis/
RUN bash /bd_build/redis/setup.sh \
&& bash /bd_build/cleanup.sh \
&& rm -rf /bd_build/redis
RUN bash /bd_build/post_setup.sh \
&& rm -rf /bd_build
#
@ -42,7 +77,8 @@ COPY --chown=azuracast:azuracast . .
RUN composer dump-autoload --optimize --classmap-authoritative \
&& touch /var/azuracast/.docker
VOLUME ["/var/azuracast/www_tmp", "/var/azuracast/uploads", "/var/azuracast/backups", "/var/azuracast/sftpgo/persist"]
VOLUME ["/var/azuracast/stations", "/var/azuracast/www_tmp", "/var/azuracast/uploads", "/var/azuracast/backups", "/var/azuracast/sftpgo/persist", "/var/azuracast/servers/shoutcast2"]
ENV PATH="${PATH}:/var/azuracast/servers/shoutcast2"
#
# END Operations as `azuracast` user
@ -50,18 +86,19 @@ VOLUME ["/var/azuracast/www_tmp", "/var/azuracast/uploads", "/var/azuracast/back
USER root
EXPOSE 80 2022
EXPOSE 8000-8999
# Sensible default environment variables.
ENV LANG="en_US.UTF-8" \
DOCKER_IS_STANDALONE="true" \
APPLICATION_ENV="production" \
ENABLE_ADVANCED_FEATURES="false" \
MYSQL_HOST="mariadb" \
MYSQL_HOST="localhost" \
MYSQL_PORT=3306 \
MYSQL_USER="azuracast" \
MYSQL_PASSWORD="azur4c457" \
MYSQL_DATABASE="azuracast" \
ENABLE_REDIS="true" \
REDIS_HOST="redis" \
REDIS_HOST="localhost" \
REDIS_PORT=6379 \
REDIS_DB=1 \
NGINX_RADIO_PORTS="default" \
@ -75,5 +112,5 @@ ENV LANG="en_US.UTF-8" \
PROFILING_EXTENSION_HTTP_IP_WHITELIST=*
# Entrypoint and default command
ENTRYPOINT ["/usr/local/bin/uptime_wait"]
CMD ["/usr/local/bin/my_init"]
ENTRYPOINT ["/usr/local/bin/my_init"]
CMD ["--no-main-command"]

View File

@ -26,8 +26,8 @@ build: # Rebuild all containers and restart
update: # Update everything (i.e. after a branch update)
docker-compose build
$(MAKE) down
docker-compose run --rm --user=azuracast web composer install
docker-compose run --rm --user=azuracast web azuracast_cli azuracast:setup:initialize
docker-compose run --rm web gosu azuracast composer install
docker-compose run --rm web azuracast_cli azuracast:setup:initialize
$(MAKE) frontend-build
$(MAKE) up

View File

@ -4,7 +4,6 @@
APPLICATION_ENV=development
LOG_LEVEL=debug
ENABLE_ADVANCED_FEATURES=true
COMPOSER_PLUGIN_MODE=false
# Limit station port range

View File

@ -39,17 +39,17 @@ AUTO_ASSIGN_PORT_MAX=8499
# 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
# Default: localhost
# MYSQL_HOST=localhost
# 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
# MYSQL_PORT=3306
# The username AzuraCast will use to connect to the database.
# Default: azuracast
MYSQL_USER=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
@ -59,7 +59,7 @@ MYSQL_PASSWORD=azur4c457
# The name of the AzuraCast database.
# Default: azuracast
MYSQL_DATABASE=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.
@ -71,12 +71,12 @@ MYSQL_RANDOM_ROOT_PASSWORD=yes
# To read the slow query log once enabled, run:
# docker-compose exec mariadb slow_queries
# Default: 0
MYSQL_SLOW_QUERY_LOG=0
# MYSQL_SLOW_QUERY_LOG=0
# 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
# MYSQL_MAX_CONNECTIONS=100
#
# Redis Configuration
@ -90,8 +90,8 @@ MYSQL_MAX_CONNECTIONS=100
# ENABLE_REDIS=true
# Name of the Redis host.
# Default: redis
# REDIS_HOST=redis
# Default: localhost
# REDIS_HOST=localhost
# Port to connect to on the Redis host.
# Default: 6379

13
bin/uptime_wait Executable file → Normal file
View File

@ -92,8 +92,10 @@ class UptimeWait
protected function checkDatabase(): bool
{
try {
$defaultHost = $this->isStandalone() ? 'localhost' : 'mariadb';
$dbOptions = [
'host' => $_ENV['MYSQL_HOST'] ?? 'mariadb',
'host' => $_ENV['MYSQL_HOST'] ?? $defaultHost,
'port' => (int)($_ENV['MYSQL_PORT'] ?? 3306),
'dbname' => $_ENV['MYSQL_DATABASE'] ?? 'azuracast',
'user' => $_ENV['MYSQL_USER'] ?? 'azuracast',
@ -119,8 +121,10 @@ class UptimeWait
protected function checkRedis(): bool
{
$defaultHost = $this->isStandalone() ? 'localhost' : 'redis';
$enableRedis = $this->envToBool($_ENV['ENABLE_REDIS'] ?? true);
$redisHost = $_ENV['REDIS_HOST'] ?? 'redis';
$redisHost = $_ENV['REDIS_HOST'] ?? $defaultHost;
$redisPort = (int)($_ENV['REDIS_PORT'] ?? 6379);
$redisDb = (int)($_ENV['REDIS_DB'] ?? 1);
@ -157,6 +161,11 @@ class UptimeWait
|| '1' === $value;
}
protected function isStandalone(): bool
{
return $this->envToBool($_ENV['DOCKER_IS_STANDALONE'] ?? false);
}
protected function println(string $line): void
{
echo $line . "\n";

View File

@ -1,28 +1,13 @@
services :
web :
build :
context : .
volumes :
services:
web:
build:
context: .
ports:
- "127.0.0.1:3306:3306"
- "127.0.0.1:6379:6379"
volumes:
- ./util/local_ssl:/etc/nginx/certs
- ./vendor:/var/azuracast/www/vendor
- .:/var/azuracast/www
extra_hosts:
- "host.docker.internal:host-gateway"
mariadb :
build :
context : ../docker-azuracast-db
ports :
- "127.0.0.1:3306:3306"
redis :
build :
context : ../docker-azuracast-redis
ports :
- "127.0.0.1:6379:6379"
stations :
build :
context : ../docker-azuracast-radio
volumes :
- ./util/local_ssl:/etc/nginx/certs

View File

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

View File

@ -12,83 +12,14 @@
services:
web:
container_name: azuracast_web
image: "ghcr.io/azuracast/web:${AZURACAST_VERSION:-latest}"
container_name: azuracast
image: "ghcr.io/azuracast/azuracast:${AZURACAST_VERSION:-latest}"
# Want to customize the HTTP/S ports? Follow the instructions here:
# https://docs.azuracast.com/en/administration/docker#using-non-standard-ports
ports:
- '${AZURACAST_HTTP_PORT:-80}:80'
- '${AZURACAST_HTTPS_PORT:-443}:443'
- '${AZURACAST_SFTP_PORT:-2022}:2022'
depends_on:
- mariadb
- stations
- redis
env_file: azuracast.env
environment:
LANG: ${LANG:-en_US.UTF-8}
AZURACAST_DC_REVISION: 13
AZURACAST_VERSION: ${AZURACAST_VERSION:-latest}
AZURACAST_SFTP_PORT: ${AZURACAST_SFTP_PORT:-2022}
NGINX_TIMEOUT: ${NGINX_TIMEOUT:-1800}
LETSENCRYPT_HOST: ${LETSENCRYPT_HOST:-}
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL:-}
PUID: ${AZURACAST_PUID:-1000}
PGID: ${AZURACAST_PGID:-1000}
volumes:
- letsencrypt:/etc/nginx/certs
- letsencrypt_acme:/etc/acme.sh
- www_vendor:/var/azuracast/www/vendor
- www_uploads:/var/azuracast/uploads
- 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: unless-stopped
ulimits: &default-ulimits
nofile:
soft: 65536
hard: 65536
logging: &default-logging
options:
max-size: "1m"
max-file: "5"
mariadb:
container_name: azuracast_mariadb
image: "ghcr.io/azuracast/db:${AZURACAST_VERSION:-latest}"
volumes:
- db_data:/var/lib/mysql
env_file: azuracast.env
networks:
- backend
restart: unless-stopped
logging: *default-logging
redis:
container_name: azuracast_redis
image: "ghcr.io/azuracast/redis:${AZURACAST_VERSION:-latest}"
sysctls:
net.core.somaxconn: 1024
volumes:
- redis_data:/data
networks:
- backend
restart: unless-stopped
logging: *default-logging
stations:
container_name: azuracast_stations
image: "ghcr.io/azuracast/radio:${AZURACAST_VERSION:-latest}"
environment:
PUID: ${AZURACAST_PUID:-1000}
PGID: ${AZURACAST_PGID:-1000}
ports:
# 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:
@ -240,24 +171,38 @@ services:
- '8490:8490'
- '8495:8495'
- '8496:8496'
env_file: azuracast.env
environment:
LANG: ${LANG:-en_US.UTF-8}
AZURACAST_DC_REVISION: 14
AZURACAST_VERSION: ${AZURACAST_VERSION:-latest}
AZURACAST_SFTP_PORT: ${AZURACAST_SFTP_PORT:-2022}
NGINX_TIMEOUT: ${NGINX_TIMEOUT:-1800}
LETSENCRYPT_HOST: ${LETSENCRYPT_HOST:-}
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL:-}
PUID: ${AZURACAST_PUID:-1000}
PGID: ${AZURACAST_PGID:-1000}
volumes:
- letsencrypt:/etc/nginx/certs
- letsencrypt_acme:/etc/acme.sh
- www_vendor:/var/azuracast/www/vendor
- www_uploads:/var/azuracast/uploads
- tmp_data:/var/azuracast/www_tmp
- station_data:/var/azuracast/stations
- shoutcast2_install:/var/azuracast/servers/shoutcast2
- letsencrypt:/etc/nginx/certs
- tmp_data:/var/azuracast/www_tmp
networks:
- frontend
- backend
init: true
- geolite_install:/var/azuracast/geoip
- sftpgo_data:/var/azuracast/sftpgo/persist
- backups:/var/azuracast/backups
- db_data:/var/lib/mysql
restart: unless-stopped
ulimits: *default-ulimits
logging: *default-logging
networks:
frontend:
driver: bridge
backend:
driver: bridge
ulimits: &default-ulimits
nofile:
soft: 65536
hard: 65536
logging: &default-logging
options:
max-size: "1m"
max-file: "5"
volumes:
db_data: { }
@ -270,5 +215,4 @@ volumes:
www_vendor: { }
www_uploads: { }
tmp_data: { }
redis_data: { }
backups: { }

View File

@ -378,6 +378,7 @@ run-installer() {
fi
curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/$AZURACAST_RELEASE_BRANCH/docker-compose.installer.yml -o docker-compose.installer.yml
docker-compose -p azuracast_installer -f docker-compose.installer.yml pull
docker-compose -p azuracast_installer -f docker-compose.installer.yml run --rm installer install "$@"
@ -454,14 +455,6 @@ install-dev() {
fi
fi
if [[ ! -d ../docker-azuracast-radio ]]; then
if ask "Clone related repositories?" Y; then
git clone https://github.com/AzuraCast/docker-azuracast-db.git ../docker-azuracast-db
git clone https://github.com/AzuraCast/docker-azuracast-redis.git ../docker-azuracast-redis
git clone https://github.com/AzuraCast/docker-azuracast-radio.git ../docker-azuracast-radio
fi
fi
if [[ ! -f docker-compose.yml ]]; then
cp docker-compose.sample.yml docker-compose.yml
fi
@ -485,7 +478,7 @@ install-dev() {
fi
docker-compose build
docker-compose run --rm --user="azuracast" web azuracast_install "$@"
docker-compose run --rm web azuracast_install "$@"
docker-compose -f frontend/docker-compose.yml build
docker-compose -f frontend/docker-compose.yml run --rm frontend npm run dev-build
@ -563,7 +556,7 @@ update() {
docker volume rm azuracast_tmp_data
docker volume rm azuracast_redis_data
docker-compose run --rm --user="azuracast" web azuracast_update "$@"
docker-compose run --rm web azuracast_update "$@"
docker-compose up -d
if ask "Clean up all stopped Docker containers and images to save space?" Y; then
@ -654,7 +647,7 @@ backup() {
.env --file .env set AZURACAST_PGID="$(id -g)"
fi
docker-compose run --rm web azuracast_cli azuracast:backup "/var/azuracast/backups/${BACKUP_FILENAME}" "$@"
docker-compose exec web azuracast_cli azuracast:backup "/var/azuracast/backups/${BACKUP_FILENAME}" "$@"
# Move from Docker volume to local filesystem
docker run --rm -v "azuracast_backups:/backup_src" \

View File

@ -34,6 +34,7 @@ class Environment
public const ASSET_URL = 'ASSETS_URL';
public const DOCKER_REVISION = 'AZURACAST_DC_REVISION';
public const DOCKER_IS_STANDALONE = 'DOCKER_IS_STANDALONE';
public const LANG = 'LANG';
@ -208,22 +209,32 @@ class Environment
return ($compareVersion >= $version);
}
public function getUriToWeb(): UriInterface
public function isDockerStandalone(): bool
{
if ($this->isDocker()) {
return $this->isDockerRevisionAtLeast(5)
? new Uri('http://web')
: new Uri('http://nginx');
if (!$this->isDocker()) {
return false;
}
return new Uri('http://127.0.0.1');
return self::envToBool($this->data[self::DOCKER_IS_STANDALONE] ?? false);
}
public function getUriToWeb(): UriInterface
{
return match (true) {
$this->isDockerStandalone() => new Uri('http://127.0.0.1'),
$this->isDockerRevisionAtLeast(5) => new Uri('http://web'),
$this->isDocker() => new Uri('http://nginx'),
default => new Uri('http://127.0.0.1')
};
}
public function getUriToStations(): UriInterface
{
return $this->isDocker()
? new Uri('http://stations')
: new Uri('http://127.0.0.1');
return match (true) {
$this->isDockerStandalone() => new Uri('http://127.0.0.1'),
$this->isDocker() => new Uri('http://stations'),
default => new Uri('http://127.0.0.1'),
};
}
public function getLang(): ?string
@ -296,11 +307,17 @@ class Environment
*/
public function getDatabaseSettings(): array
{
$defaultHost = match (true) {
$this->isDockerStandalone() => 'localhost',
$this->isDocker() => 'mariadb',
default => 'localhost'
};
return [
'host' => $this->data[self::DB_HOST] ?? ($this->isDocker() ? 'mariadb' : 'localhost'),
'port' => (int)($this->data[self::DB_PORT] ?? 3306),
'dbname' => $this->data[self::DB_NAME] ?? 'azuracast',
'user' => $this->data[self::DB_USER] ?? 'azuracast',
'host' => $this->data[self::DB_HOST] ?? $defaultHost,
'port' => (int)($this->data[self::DB_PORT] ?? 3306),
'dbname' => $this->data[self::DB_NAME] ?? 'azuracast',
'user' => $this->data[self::DB_USER] ?? 'azuracast',
'password' => $this->data[self::DB_PASSWORD] ?? 'azur4c457',
];
}
@ -315,10 +332,16 @@ class Environment
*/
public function getRedisSettings(): array
{
$defaultHost = match (true) {
$this->isDockerStandalone() => 'localhost',
$this->isDocker() => 'redis',
default => 'localhost'
};
return [
'host' => $this->data[self::REDIS_HOST] ?? ($this->isDocker() ? 'redis' : 'localhost'),
'host' => $this->data[self::REDIS_HOST] ?? $defaultHost,
'port' => (int)($this->data[self::REDIS_PORT] ?? 6379),
'db' => (int)($this->data[self::REDIS_DB] ?? 1),
'db' => (int)($this->data[self::REDIS_DB] ?? 1),
];
}
@ -337,6 +360,15 @@ class Environment
return $this->data[self::PROFILING_EXTENSION_HTTP_KEY] ?? 'dev';
}
public static function getDefaultsForEnvironment(Environment $existingEnv): self
{
return new self([
self::IS_CLI => $existingEnv->isCli(),
self::IS_DOCKER => $existingEnv->isDocker(),
self::DOCKER_IS_STANDALONE => $existingEnv->isDockerStandalone(),
]);
}
public static function envToBool(mixed $value): bool
{
if (is_bool($value)) {

View File

@ -102,11 +102,11 @@ class InstallCommand extends Command
$locale = SupportedLocales::getValidLocale($azuracastEnv[Environment::LANG] ?? null);
$locale->register($this->environment);
$envConfig = EnvFile::getConfiguration();
$env->setFromDefaults();
$envConfig = EnvFile::getConfiguration($this->environment);
$env->setFromDefaults($this->environment);
$azuracastEnvConfig = AzuraCastEnvFile::getConfiguration();
$azuracastEnv->setFromDefaults();
$azuracastEnvConfig = AzuraCastEnvFile::getConfiguration($this->environment);
$azuracastEnv->setFromDefaults($this->environment);
// Apply values passed via flags
if (null !== $releaseChannel) {
@ -156,6 +156,16 @@ class InstallCommand extends Command
$azuracastEnv['MYSQL_RANDOM_ROOT_PASSWORD'] = 'yes';
}
// Special fixes for transitioning to standalone installations.
if ($this->environment->isDockerStandalone()) {
if ('mariadb' === $azuracastEnv['MYSQL_HOST']) {
unset($azuracastEnv['MYSQL_HOST']);
}
if ('redis' === $azuracastEnv['REDIS_HOST']) {
unset($azuracastEnv['REDIS_HOST']);
}
}
// Display header messages
if ($isNewInstall) {
$io->title(
@ -253,8 +263,8 @@ class InstallCommand extends Command
__('Writing configuration files...')
);
$envStr = $env->writeToFile();
$azuracastEnvStr = $azuracastEnv->writeToFile();
$envStr = $env->writeToFile($this->environment);
$azuracastEnvStr = $azuracastEnv->writeToFile($this->environment);
if ($io->isVerbose()) {
$io->section($env->getBasename());
@ -289,10 +299,12 @@ class InstallCommand extends Command
$sampleFile = $this->environment->getBaseDirectory() . '/docker-compose.sample.yml';
$yaml = Yaml::parseFile($sampleFile);
$isStandalone = $this->environment->isDockerStandalone();
// Parse port listing and convert into YAML format.
$ports = $env['AZURACAST_STATION_PORTS'] ?? '';
$envConfig = $env::getConfiguration();
$envConfig = $env::getConfiguration($this->environment);
$defaultPorts = $envConfig['AZURACAST_STATION_PORTS']['default'];
if (!empty($ports) && 0 !== strcmp($ports, $defaultPorts)) {
@ -316,7 +328,18 @@ class InstallCommand extends Command
}
if (!empty($yamlPorts)) {
$yaml['services']['stations']['ports'] = $yamlPorts;
if ($isStandalone) {
$existingPorts = [];
foreach ($yaml['services']['ports'] as $port) {
if (str_starts_with('$', $port)) {
$existingPorts[] = $port;
}
}
$yaml['services']['web']['ports'] = array_merge($existingPorts, $yamlPorts);
} else {
$yaml['services']['stations']['ports'] = $yamlPorts;
}
}
if (!empty($nginxRadioPorts)) {
$nginxRadioPortsStr = '(' . implode('|', $nginxRadioPorts) . ')';
@ -329,9 +352,11 @@ class InstallCommand extends Command
}
// Remove Redis if it's not enabled.
$enableRedis = $azuracastEnv->getAsBool(Environment::ENABLE_REDIS, true);
if (!$enableRedis) {
unset($yaml['services']['redis']);
if (!$isStandalone) {
$enableRedis = $azuracastEnv->getAsBool(Environment::ENABLE_REDIS, true);
if (!$enableRedis) {
unset($yaml['services']['redis']);
}
}
// Remove privileged-mode settings if not enabled.

View File

@ -32,12 +32,12 @@ abstract class AbstractEnvFile implements ArrayAccess
return basename($this->path);
}
public function setFromDefaults(): void
public function setFromDefaults(Environment $environment): void
{
$currentVars = array_filter($this->data);
$defaults = [];
foreach (static::getConfiguration() as $key => $keyInfo) {
foreach (static::getConfiguration($environment) as $key => $keyInfo) {
if (isset($keyInfo['default'])) {
$defaults[$key] = $keyInfo['default'] ?? null;
}
@ -82,7 +82,7 @@ abstract class AbstractEnvFile implements ArrayAccess
unset($this->data[$offset]);
}
public function writeToFile(): string
public function writeToFile(Environment $environment): string
{
$values = array_filter($this->data);
@ -93,7 +93,7 @@ abstract class AbstractEnvFile implements ArrayAccess
'',
];
foreach (static::getConfiguration() as $key => $keyInfo) {
foreach (static::getConfiguration($environment) as $key => $keyInfo) {
$envFile[] = '# ' . ($keyInfo['name'] ?? $key);
if (!empty($keyInfo['description'])) {
@ -180,7 +180,7 @@ abstract class AbstractEnvFile implements ArrayAccess
/**
* @return mixed[]
*/
abstract public static function getConfiguration(): array;
abstract public static function getConfiguration(Environment $environment): array;
abstract public static function buildPathFromBase(string $baseDir): string;

View File

@ -14,12 +14,12 @@ use function __;
class AzuraCastEnvFile extends AbstractEnvFile
{
/** @inheritDoc */
public static function getConfiguration(): array
public static function getConfiguration(Environment $environment): array
{
static $config = null;
if (null === $config) {
$emptyEnv = new Environment([]);
$emptyEnv = Environment::getDefaultsForEnvironment($environment);
$defaults = $emptyEnv->toArray();
$langOptions = [];

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Installer\EnvFiles;
use App\Environment;
use App\Radio\Configuration;
use function __;
@ -11,7 +12,7 @@ use function __;
class EnvFile extends AbstractEnvFile
{
/** @inheritDoc */
public static function getConfiguration(): array
public static function getConfiguration(Environment $environment): array
{
static $config = null;

View File

@ -125,7 +125,11 @@ class ConfigWriter implements EventSubscriberInterface
$configDir = $station->getRadioConfigDir();
$pidfile = $configDir . DIRECTORY_SEPARATOR . 'liquidsoap.pid';
$telnetBindAddr = $this->environment->isDocker() ? '0.0.0.0' : '127.0.0.1';
$telnetBindAddr = match (true) {
$this->environment->isDockerStandalone() => '127.0.0.1',
$this->environment->isDocker() => '0.0.0.0',
default => '127.0.0.1',
};
$telnetPort = $this->liquidsoap->getTelnetPort($station);
$stationTz = self::cleanUpString($station->getTimezone());

View File

@ -13,7 +13,8 @@ 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 \
mkdir -p /var/azuracast/www /var/azuracast/stations /var/azuracast/servers/shoutcast2 \
/var/azuracast/backups /var/azuracast/www_tmp \
/var/azuracast/uploads /var/azuracast/geoip /var/azuracast/dbip
chown -R azuracast:azuracast /var/azuracast

View File

@ -0,0 +1,16 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
chmod -R a+x /usr/local/bin
chmod -R +x /etc/my_init.d
chmod -R +x /etc/my_init.pre_shutdown.d
chmod -R +x /etc/my_init.post_shutdown.d
ln -s /etc/service.minimal/* /etc/service
ln -s /etc/service.full/* /etc/service
chmod -R +x /etc/service.minimal
chmod -R +x /etc/service.full
chmod -R +x /etc/service

View File

@ -67,4 +67,14 @@ 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/
ln -s /etc/container_environment.sh /etc/profile.d/
# Install runit and other common scripts.
$minimal_apt_get_install runit gosu curl wget tar zip unzip git rsync tzdata gpg-agent openssh-client
# Add scripts
cp -rT /bd_build/scripts/ /usr/local/bin
chmod -R a+x /usr/local/bin
mkdir -p /etc/service.full/
mkdir -p /etc/service.minimal/

View File

@ -30,41 +30,33 @@ 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)
@ -75,14 +67,12 @@ def 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
@ -101,7 +91,6 @@ def import_envvars(clear_existing_environment=True, override_existing_environmen
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
@ -118,7 +107,6 @@ def export_envvars(to_dir=True):
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:
@ -130,16 +118,13 @@ def shquote(s):
# 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
@ -172,7 +157,6 @@ def waitpid_reap_other_children(pid):
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:
@ -198,7 +182,6 @@ def stop_child_process(name, pid, signo=signal.SIGTERM, time_limit=KILL_PROCESS_
finally:
signal.alarm(0)
def run_command_killable(*argv):
filename = argv[0]
status = None
@ -216,13 +199,11 @@ def run_command_killable(*argv):
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:
@ -264,7 +245,6 @@ def run_startup_files():
info("Running /etc/rc.local...")
run_command_killable_and_import_envvars("/etc/rc.local")
def run_pre_shutdown_scripts():
debug("Running pre-shutdown scripts...")
@ -275,7 +255,6 @@ def run_pre_shutdown_scripts():
info("Running %s..." % filename)
run_command_killable(filename)
def run_post_shutdown_scripts():
debug("Running post-shutdown scripts...")
@ -286,31 +265,26 @@ def run_post_shutdown_scripts():
info("Running %s..." % filename)
run_command_killable(filename)
def start_runit():
def start_runit(runit_services_dir):
info("Booting runit daemon...")
pid = os.spawnl(os.P_NOWAIT, "/usr/bin/runsvdir", "/usr/bin/runsvdir",
"-P", "/etc/service")
pid = os.spawnl(os.P_NOWAIT, "/usr/bin/runsvdir", "/usr/bin/runsvdir", "-P", runit_services_dir)
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):
def shutdown_runit_services(runit_services_dir, 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)
os.system("/usr/bin/sv -w %d force-stop %s/* > /dev/null" % (KILL_PROCESS_TIMEOUT, runit_services_dir))
def wait_for_runit_services():
def wait_for_runit_services(runit_services_dir):
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
done = os.system("/usr/bin/sv status %s/* | grep -q '^run:'" % runit_services_dir) != 0
if not done:
time.sleep(0.1)
# According to https://github.com/phusion/baseimage-docker/issues/315
@ -318,32 +292,28 @@ def wait_for_runit_services():
# 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")
shutdown_runit_services(runit_services_dir, True)
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()
runit_services_dir = '/etc/service.minimal'
if len(args.main_command) == 0 or args.no_main_command:
runit_services_dir = '/etc/service'
runit_pid = start_runit(runit_services_dir)
try:
exit_status = None
if len(args.main_command) == 0:
if len(args.main_command) == 0 or args.no_main_command:
runit_exited, exit_code = wait_for_runit_or_interrupt(runit_pid)
if runit_exited:
if exit_code is None:
@ -353,52 +323,63 @@ def main(args):
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)
main_command = ["uptime_wait"] + args.main_command
info("Running %s..." % " ".join(main_command))
pid = os.spawnvp(os.P_NOWAIT, main_command[0], 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])
info("%s exited with unknown status." % 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))
info("%s exited with status %d." % (main_command[0], exit_status))
except KeyboardInterrupt:
stop_child_process(args.main_command[0], pid)
stop_child_process(main_command[0], pid)
raise
except BaseException:
warn("An error occurred. Aborting.")
stop_child_process(args.main_command[0], pid)
stop_child_process(main_command[0], pid)
raise
sys.exit(exit_status)
finally:
if not args.skip_runit:
run_pre_shutdown_scripts()
shutdown_runit_services()
shutdown_runit_services(runit_services_dir)
if not runit_exited:
stop_child_process("runit daemon", runit_pid)
wait_for_runit_services()
wait_for_runit_services(runit_services_dir)
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',
parser.add_argument('--no-main-command', dest='no_main_command',
action='store_const', const=True, default=False,
help='Install the insecure SSH key')
help='Flag to provide as main command in the absence of one.')
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
@ -410,6 +391,7 @@ if args.skip_runit and len(args.main_command) == 0:
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:
@ -417,4 +399,4 @@ except KeyboardInterrupt:
exit(2)
finally:
if args.kill_all_on_exit:
kill_all_processes(KILL_ALL_PROCESSES_TIMEOUT)
kill_all_processes(KILL_ALL_PROCESSES_TIMEOUT)

View File

@ -0,0 +1,11 @@
[mysqld]
character-set-server=utf8mb4
slow_query_log = {{ default .Env.MYSQL_SLOW_QUERY_LOG "0" }}
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.2
max_connections = {{ default .Env.MYSQL_MAX_CONNECTIONS "100" }}
[client]
default-character-set=utf8mb4

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
MYSQL_USER="${MYSQL_USER:-azuracast}"
MYSQL_PASSWORD="${MYSQL_PASSWORD:-azur4c457}"
MYSQL_DATABASE="${MYSQL_DATABASE:-azuracast}"
exec gosu mysql mysql -u${MYSQL_USER} -p${MYSQL_PASSWORD} -D ${MYSQL_DATABASE}

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
MYSQL_USER="${MYSQL_USER:-azuracast}"
MYSQL_PASSWORD="${MYSQL_PASSWORD:-azur4c457}"
MYSQL_DATABASE="${MYSQL_DATABASE:-azuracast}"
cat $1 | mysql -u${MYSQL_USER} -p${MYSQL_PASSWORD} -D ${MYSQL_DATABASE}

View File

@ -0,0 +1,124 @@
#!/usr/bin/env bash
if [ "$(id -u)" = "0" ]; then
echo "Switching to dedicated user 'mysql'"
exec gosu mysql "$BASH_SOURCE" "$@"
fi
# usage: file_env VAR [DEFAULT]
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
file_env() {
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
mysql_error "Both $var and $fileVar are set (but are exclusive)"
fi
local val="$def"
if [ "${!var:-}" ]; then
val="${!var}"
elif [ "${!fileVar:-}" ]; then
val="$(< "${!fileVar}")"
fi
export "$var"="$val"
unset "$fileVar"
}
# set MARIADB_xyz from MYSQL_xyz when MARIADB_xyz is unset
# and make them the same value (so user scripts can use either)
_mariadb_file_env() {
local var="$1"; shift
local maria="MARIADB_${var#MYSQL_}"
file_env "$var" "$@"
file_env "$maria" "${!var}"
if [ "${!maria:-}" ]; then
export "$var"="${!maria}"
fi
}
# SQL escape the string $1 to be placed in a string literal.
# escape, \ followed by '
docker_sql_escape_string_literal() {
local newline=$'\n'
local escaped=${1//\\/\\\\}
escaped="${escaped//$newline/\\n}"
echo "${escaped//\'/\\\'}"
}
mysql_get_config() {
local conf="$1"; shift
mysqld --verbose --help 2>/dev/null \
| awk -v conf="$conf" '$1 == conf && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }'
}
# Build env vars
DATADIR="$(mysql_get_config 'datadir')"
SOCKET="$(mysql_get_config 'socket')"
_mariadb_file_env 'MYSQL_ROOT_HOST' '%'
_mariadb_file_env 'MYSQL_ROOT_PASSWORD'
: "${MARIADB_ALLOW_EMPTY_ROOT_PASSWORD:=${MYSQL_ALLOW_EMPTY_PASSWORD:-}}"
export MYSQL_ALLOW_EMPTY_PASSWORD="$MARIADB_ALLOW_EMPTY_ROOT_PASSWORD" MARIADB_ALLOW_EMPTY_ROOT_PASSWORD
: "${MARIADB_RANDOM_ROOT_PASSWORD:=${MYSQL_RANDOM_ROOT_PASSWORD:-}}"
export MYSQL_RANDOM_ROOT_PASSWORD="$MARIADB_RANDOM_ROOT_PASSWORD" MARIADB_RANDOM_ROOT_PASSWORD
: "${MARIADB_INITDB_SKIP_TZINFO:=${MYSQL_INITDB_SKIP_TZINFO:-}}"
export MYSQL_INITDB_SKIP_TZINFO="$MARIADB_INITDB_SKIP_TZINFO" MARIADB_INITDB_SKIP_TZINFO
if [ -z "$MARIADB_ROOT_PASSWORD" -a -z "$MARIADB_ALLOW_EMPTY_ROOT_PASSWORD" -a -z "$MARIADB_RANDOM_ROOT_PASSWORD" ]; then
mysql_error $'Database is uninitialized and password option is not specified\n\tYou need to specify one of MARIADB_ROOT_PASSWORD, MARIADB_ALLOW_EMPTY_ROOT_PASSWORD and MARIADB_RANDOM_ROOT_PASSWORD'
fi
# Spin up temp server
echo "Starting temporary MariaDB server..."
mysqld --skip-networking --skip-grant-tables --socket="${SOCKET}" &
echo "Waiting for server startup"
for i in {30..0}; do
if mysql --protocol=socket -hlocalhost -uroot --socket="${SOCKET}" --database=mysql <<<'SELECT 1'; then
break
fi
sleep 1
done
if [ "$i" = 0 ]; then
echo "Unable to start temporary server."
exit 1
fi
# Try password reset
echo "Resetting root password..."
if [ -n "$MARIADB_RANDOM_ROOT_PASSWORD" ]; then
MARIADB_ROOT_PASSWORD="$(pwgen --numerals --capitalize --symbols --remove-chars="'\\" -1 32)"
export MARIADB_ROOT_PASSWORD MYSQL_ROOT_PASSWORD=$MARIADB_ROOT_PASSWORD
echo "GENERATED ROOT PASSWORD: $MARIADB_ROOT_PASSWORD"
fi
# Sets root password and creates root users for non-localhost hosts
rootCreate=
rootPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_ROOT_PASSWORD}" )
# default root to listen for connections from anywhere
if [ -n "$MARIADB_ROOT_HOST" ] && [ "$MARIADB_ROOT_HOST" != 'localhost' ]; then
# no, we don't care if read finds a terminating character in this heredoc
# https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151
read -r -d '' rootCreate <<-EOSQL || true
ALTER USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${rootPasswordEscaped}' ;
GRANT ALL ON *.* TO 'root'@'${MARIADB_ROOT_HOST}' WITH GRANT OPTION ;
EOSQL
fi
# tell docker_process_sql to not use MARIADB_ROOT_PASSWORD since it is just now being set
# --binary-mode to save us from the semi-mad users go out of their way to confuse the encoding.
mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" --database=mysql --binary-mode <<-EOSQL
FLUSH PRIVILEGES ;
ALTER USER 'root'@'localhost' IDENTIFIED BY '${rootPasswordEscaped}' ;
GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ;
${rootCreate}
EOSQL
echo "Password reset complete."

View File

@ -0,0 +1,9 @@
#!/bin/bash
if [ ${MYSQL_SLOW_QUERY_LOG} != 1 ]; then
echo "MariaDB Slow Query Log is not currently enabled on this instance."
echo "Set MYSQL_SLOW_QUERY_LOG=1 in your environment variables (azuracast.env), then restart to enable."
exit 1
fi
exec gosu mysql mysqldumpslow /var/log/mysql/slow.log

View File

@ -0,0 +1,37 @@
#!/usr/bin/env bash
if [ "$(id -u)" = "0" ]; then
echo "Switching to dedicated user 'mysql'"
exec gosu mysql "$BASH_SOURCE" "$@"
fi
mysql_get_config() {
local conf="$1"; shift
mysqld --verbose --help 2>/dev/null \
| awk -v conf="$conf" '$1 == conf && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }'
}
DATADIR="$(mysql_get_config 'datadir')"
SOCKET="$(mysql_get_config 'socket')"
echo "Starting temporary MariaDB server..."
mysqld --skip-networking --skip-grant-tables --socket="${SOCKET}" &
echo "Waiting for server startup"
for i in {30..0}; do
if mysql --protocol=socket -hlocalhost -uroot --socket="${SOCKET}" --database=mysql <<<'SELECT 1'; then
break
fi
sleep 1
done
if [ "$i" = 0 ]; then
echo "Unable to start temporary server."
exit 1
fi
echo "Upgrading instance (if necessary)..."
mariadb-upgrade --verbose --protocol=socket -hlocalhost -uroot --socket="${SOCKET}"
echo "Upgrade complete; Shutting down temporary MariaDB server..."

View File

@ -0,0 +1,3 @@
#!/bin/bash
exec mysqladmin ping -h localhost

View File

@ -0,0 +1,5 @@
#!/bin/bash
dockerize -template /etc/mysql/db.cnf.tmpl:/etc/mysql/conf.d/db.cnf
exec gosu mysql mysqld

View File

@ -0,0 +1,20 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
apt-get update
# Install common scripts
cp -rT /bd_build/mariadb/scripts/ /usr/local/bin
cp -rT /bd_build/mariadb/startup_scripts/. /etc/my_init.d/
cp -rT /bd_build/mariadb/service.minimal/. /etc/service.minimal/
# cp -rT /bd_build/mariadb/service.full/. /etc/service.full/
# Run service setup for all setup scripts
for f in /bd_build/mariadb/setup/*.sh; do
bash "$f" -H
done

View File

@ -0,0 +1,14 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
$minimal_apt_get_install tzdata
echo "1" >> /etc/container_environment/MARIADB_AUTO_UPGRADE
cp /bd_build/mariadb/mariadb/db.sql /docker-entrypoint-initdb.d/00-azuracast.sql
cp /bd_build/mariadb/mariadb/db.cnf.tmpl /etc/mysql/db.cnf.tmpl
mv /usr/local/bin/healthcheck.sh /usr/local/bin/db_healthcheck.sh
mv /usr/local/bin/docker-entrypoint.sh /usr/local/bin/db_entrypoint.sh

View File

@ -0,0 +1,9 @@
#!/bin/bash
# If the MariaDB host is anything but localhost, disable MariaDB on this container.
if [ "$MYSQL_HOST" != "localhost" ]; then
echo "MariaDB host is not localhost; disabling MariaDB..."
rm -rf /etc/service/mariadb
rm -rf /etc/service.minimal/mariadb
fi

View File

@ -0,0 +1,52 @@
#!/bin/bash
if [ ! -f /etc/service/mariadb/run ]; then
echo "MariaDB disabled. Skipping DB initialization..."
exit 0
fi
source /usr/local/bin/db_entrypoint.sh
set -- mysqld
mysql_note "Initial DB setup..."
mysql_check_config "$@"
# Load various environment variables
docker_setup_env "$@"
docker_create_db_directories
# If container is started as root user, restart as dedicated mysql user
if [ "$(id -u)" = "0" ]; then
mysql_note "Switching to dedicated user 'mysql'"
exec gosu mysql "${BASH_SOURCE[0]}" "$@"
fi
# there's no database, so it needs to be initialized
if [ -z "$DATABASE_ALREADY_EXISTS" ]; then
docker_verify_minimum_env
# check dir permissions to reduce likelihood of half-initialized database
ls /docker-entrypoint-initdb.d/ > /dev/null
docker_init_database_dir "$@"
mysql_note "Starting temporary server"
docker_temp_server_start "$@"
mysql_note "Temporary server started."
docker_setup_db
docker_process_init_files /docker-entrypoint-initdb.d/*
mysql_note "Stopping temporary server"
docker_temp_server_stop
mysql_note "Temporary server stopped"
echo
mysql_note "MariaDB init process done. Ready for start up."
echo
# MDEV-27636 mariadb_upgrade --check-if-upgrade-is-needed cannot be run offline
#elif mysql_upgrade --check-if-upgrade-is-needed; then
elif _check_if_upgrade_is_needed; then
docker_mariadb_upgrade "$@"
fi

View File

@ -0,0 +1,6 @@
save ""
appendonly no
maxmemory 128mb
maxmemory-policy volatile-lfu

View File

@ -0,0 +1,3 @@
#!/bin/bash
exec gosu redis redis-server /etc/redis/redis.conf

View File

@ -0,0 +1,20 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
apt-get update
# Install common scripts
# cp -rT /bd_build/redis/scripts/ /usr/local/bin
cp -rT /bd_build/redis/startup_scripts/. /etc/my_init.d/
cp -rT /bd_build/redis/service.minimal/. /etc/service.minimal/
# cp -rT /bd_build/redis/service.full/. /etc/service.full/
# Run service setup for all setup scripts
for f in /bd_build/redis/setup/*.sh; do
bash "$f" -H
done

View File

@ -0,0 +1,12 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
add-apt-repository -y ppa:chris-lea/redis-server
apt-get update
$minimal_apt_get_install redis-server
cp /bd_build/redis/redis/redis.conf /etc/redis/redis.conf
chown redis:redis /etc/redis/redis.conf

View File

@ -0,0 +1,17 @@
#!/bin/bash
bool() {
case "$1" in
Y* | y* | true | TRUE | 1) return 0 ;;
esac
return 1
}
# If Redis is expressly disabled or the host is anything but localhost, disable Redis on this container.
ENABLE_REDIS=${ENABLE_REDIS:-false}
if [ "$REDIS_HOST" != "localhost" ] || [ bool "$ENABLE_REDIS" ]; then
echo "Redis is disabled or host is not localhost; disabling Redis..."
rm -rf /etc/service/redis
rm -rf /etc/service.minimal/redis
fi

View File

@ -0,0 +1,26 @@
#!/bin/bash
set -e
set -x
opam init --disable-sandboxing -a --bare && opam switch create 4.13.1
# Pin specific commit of Liquidsoap
opam pin add --no-action liquidsoap https://github.com/savonet/liquidsoap.git#af311dc8ee57e3e7d3f637ea23af4096fd57820d
opam install -y ladspa.0.2.2 ffmpeg.1.1.1 ffmpeg-avutil.1.1.1 ffmpeg-avcodec.1.1.1 ffmpeg-avdevice.1.1.1 \
ffmpeg-av.1.1.1 ffmpeg-avfilter.1.1.1 ffmpeg-swresample.1.1.1 ffmpeg-swscale.1.1.1 frei0r.0.1.2 \
samplerate.0.1.6 taglib.0.3.9 mad.0.5.2 faad.0.5.0 fdkaac.0.3.2 lame.0.3.5 vorbis.0.8.0 cry.0.6.6 \
flac.0.3.0 opus.0.2.1 dtools.0.4.4 duppy.0.9.2 ocurl.0.9.2 ssl.0.5.10 \
liquidsoap
# Have Liquidsoap build its own chroot.
mkdir -p /tmp/liquidsoap
/var/azuracast/.opam/4.13.1/bin/liquidsoap /bd_build/liquidsoap/build_chroot.liq || true
# Clear entire OPAM directory
rm -rf /var/azuracast/.opam
cp -r /tmp/liquidsoap/var/azuracast/.opam /var/azuracast/.opam
rm -rf /tmp/liquidsoap

View File

@ -0,0 +1,2 @@
liquidsoap.chroot.make('/tmp/liquidsoap')
shutdown()

View File

@ -0,0 +1,3 @@
#!/bin/sh
exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf

View File

@ -0,0 +1,20 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
apt-get update
# Install common scripts
# cp -rT /bd_build/stations/scripts/ /usr/local/bin
# cp -rT /bd_build/stations/startup_scripts/. /etc/my_init.d/
cp -rT /bd_build/stations/service.minimal/. /etc/service.minimal/
# cp -rT /bd_build/stations/service.full/. /etc/service.full/
# Run service setup for all setup scripts
for f in /bd_build/stations/setup/*.sh; do
bash "$f" -H
done

View File

@ -0,0 +1,10 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
# Only install Icecast deps (Icecast is handled by another container).
$minimal_apt_get_install libxml2 libxslt1-dev libvorbis-dev
# SSL self-signed cert generation
$minimal_apt_get_install openssl

View File

@ -0,0 +1,40 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
# Packages required by Liquidsoap
$minimal_apt_get_install libao-dev libasound2-dev libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev \
libavutil-dev libfaad-dev libfdk-aac-dev libflac-dev libfreetype-dev libgd-dev libjack-dev \
libjpeg-dev liblo-dev libmad0-dev libmagic-dev libmp3lame-dev libopus-dev libpng-dev libportaudio2 \
libpulse-dev libsamplerate0-dev libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev libshine-dev libsoundtouch-dev libspeex-dev \
libsrt-dev libswresample-dev libswscale-dev libtag1-dev libtheora-dev libtiff-dev libx11-dev libxpm-dev bubblewrap ffmpeg
# Optional audio plugins
$minimal_apt_get_install frei0r-plugins-dev ladspa-sdk multimedia-audio-plugins swh-plugins tap-plugins lsp-plugins-ladspa
# Per-architecture LS installs
ARCHITECTURE=amd64
if [ "$(uname -m)" = "aarch64" ]; then
ARCHITECTURE=arm64
fi
# Adding this comment to trigger an uncached re-pull of this deb file.
wget -O /tmp/liquidsoap.deb "https://github.com/savonet/liquidsoap/releases/download/v2.0.3/liquidsoap_2.0.3-ubuntu-focal-2_${ARCHITECTURE}.deb"
dpkg -i /tmp/liquidsoap.deb
apt-get install -y -f --no-install-recommends
rm -f /tmp/liquidsoap.deb
ln -s /usr/bin/liquidsoap /usr/local/bin/liquidsoap
# To do a pinned install, uncomment and customize below
# else
# $minimal_apt_get_install build-essential libssl-dev libcurl4-openssl-dev m4 ocaml opam autoconf automake
#
# sudo -u azuracast bash ../liquidsoap/build_as_azuracast.sh
# ln -s /var/azuracast/.opam/4.13.1/bin/liquidsoap /usr/local/bin/liquidsoap
# chmod a+x /usr/local/bin/liquidsoap
# apt-get purge -y build-essential libssl-dev libcurl4-openssl-dev m4 ocaml opam autoconf automake
# fi

View File

@ -0,0 +1,12 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
# $minimal_apt_get_install python3-minimal python3-pip
# pip3 install setuptools supervisor
$minimal_apt_get_install supervisor
# mkdir -p /etc/supervisor
cp /bd_build/stations/supervisor/supervisord.conf /etc/supervisor/supervisord.conf

View File

@ -0,0 +1,25 @@
[unix_http_server]
file=/tmp/supervisor.sock ; (the path to the socket file)
[inet_http_server] ; inet (TCP) server disabled by default
port=127.0.0.1:9001 ; (ip_address:port specifier, *:port for all iface)
[supervisord]
user=root
logfile=/var/azuracast/www_tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=info ; (log level;default info; others: debug,warn,trace)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=true ; (start in foreground if true;default false)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
[include]
files = /var/azuracast/stations/*/config/supervisord.conf conf.d/*

View File

@ -1,11 +1,9 @@
{{if isTrue .Env.ENABLE_REDIS }}
upstream redis_server {
nchan_redis_server "redis://redis:6379";
nchan_redis_server "redis://localhost:6379";
}
{{end}}
resolver 127.0.0.11;
server {
listen 9010;
@ -130,7 +128,7 @@ server {
proxy_connect_timeout 60;
proxy_set_header Host localhost:$1;
proxy_pass http://stations:$1/$3?$args;
proxy_pass http://127.0.0.1:$1/$3?$args;
}
# Reverse proxy the Liquidsoap harbor inputs to allow for streaming.
@ -140,11 +138,9 @@ server {
location ~ ^/radio/{{ .Env.NGINX_WEBDJ_PORTS }}(/?)(.*)$ {
{{ end }}
resolver 127.0.0.11;
include proxy_params;
proxy_pass http://stations:$1/$3;
proxy_pass http://127.0.0.1:$1/$3;
}
# pub/sub endpoints

View File

@ -15,6 +15,7 @@ pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 100
pm.status_path = /status
pm.process_idle_timeout = 60s
chdir = /

View File

@ -1,5 +0,0 @@
#!/bin/bash
echo 'Spinning up Beanstalkd process...'
exec sudo -E -u azuracast beanstalkd -p 11300 -z 262140

View File

@ -1,5 +0,0 @@
#!/bin/bash
source /etc/php/.version
exec /usr/sbin/php-fpm${PHP_VERSION} -F --fpm-config /etc/php/${PHP_VERSION}/fpm/php-fpm.conf -c /etc/php/${PHP_VERSION}/fpm/

View File

@ -1,3 +0,0 @@
#!/bin/bash
exec sudo -E -u azuracast php /var/azuracast/www/bin/console azuracast:sync:nowplaying

View File

@ -1,3 +0,0 @@
#!/bin/bash
exec sudo -E -u azuracast php /var/azuracast/www/bin/console queue:process --worker-name=app_worker_0

View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
if [ $(whoami) != 'azuracast' ]; then
echo 'This script must be run as the "azuracast" user. Rerunning...'
exec gosu azuracast azuracast_ci "$@"
fi
azuracast_install || exit 1
cd /var/azuracast/www
composer codeception-no-coverage

View File

@ -2,7 +2,7 @@
if [ `whoami` != 'azuracast' ]; then
echo 'This script must be run as the "azuracast" user. Rerunning...'
exec sudo -E -u azuracast azuracast_cli "$@"
exec gosu azuracast azuracast_cli "$@"
fi
cd /var/azuracast/www

View File

@ -9,7 +9,7 @@ bool() {
if [ $(whoami) != 'azuracast' ]; then
echo 'This script must be run as the "azuracast" user. Rerunning...'
exec sudo -E -u azuracast azuracast_install "$@"
exec gosu azuracast azuracast_install "$@"
fi
echo "AzuraCast Setup"

View File

@ -9,7 +9,7 @@ bool() {
if [ $(whoami) != 'azuracast' ]; then
echo 'This script must be run as the "azuracast" user. Rerunning...'
exec sudo -E -u azuracast azuracast_restore "$@"
exec gosu azuracast azuracast_restore "$@"
fi
echo "AzuraCast Backup Restore"

View File

@ -2,4 +2,4 @@
source /etc/container_environment.sh
exec sudo -E -u azuracast "$@" >/proc/1/fd/1 2>/proc/1/fd/2
exec gosu azuracast "$@" >/proc/1/fd/1 2>/proc/1/fd/2

View File

@ -1,6 +1,8 @@
#!/usr/bin/env bash
exec /usr/sbin/tmpreaper 12h --protect '.tmpreaper' --verbose \
/tmp \
/tmp/app_nginx_client \
/tmp/app_fastcgi_temp \
/var/azuracast/stations/*/temp \
> /proc/1/fd/1 2> /proc/1/fd/2

View File

@ -1,10 +1,6 @@
#!/bin/bash
set -e
if [[ -f /var/azuracast/www/bin/uptime_wait ]]; then
if ! php /var/azuracast/www/bin/uptime_wait; then
exit 1
fi
fi
gosu azuracast php /var/azuracast/www/bin/uptime_wait || exit 1
exec "$@"

View File

@ -1,5 +1,7 @@
#!/bin/bash
sv -w 45 check nginx || exit 1
# Acme loading script
# Uses code from:
# https://github.com/nginx-proxy/acme-companion/blob/main/app/letsencrypt_service

View File

@ -0,0 +1,5 @@
#!/bin/bash
echo 'Spinning up Beanstalkd process...'
exec gosu azuracast beanstalkd -p 11300 -z 262140

View File

@ -1,8 +1,10 @@
#!/bin/sh
sv -w 30 check php-fpm || exit 1
# 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
exec /usr/sbin/cron -f

View File

@ -1,5 +1,7 @@
#!/bin/bash
sv -w 30 check php-fpm || exit 1
bool() {
case "$1" in
Y* | y* | true | TRUE | 1) return 0 ;;

View File

@ -0,0 +1,3 @@
#!/bin/bash
exec php-fpm-healthcheck

View File

@ -0,0 +1,25 @@
#!/bin/bash
source /etc/container_environment.sh
if [ -f /etc/service/mariadb/run ]; then
sv -w 30 check mariadb || exit 1
fi
if [ -f /etc/service/redis/run ]; then
sv -w 30 check redis || exit 1
fi
# Set up PHP config
dockerize -template "/etc/php/${PHP_VERSION}/fpm/05-azuracast.ini.tmpl:/etc/php/${PHP_VERSION}/fpm/conf.d/05-azuracast.ini" \
-template "/etc/php/${PHP_VERSION}/fpm/www.conf.tmpl:/etc/php/${PHP_VERSION}/fpm/pool.d/www.conf" \
cp /etc/php/${PHP_VERSION}/fpm/conf.d/05-azuracast.ini /etc/php/${PHP_VERSION}/cli/conf.d/05-azuracast.ini
# Wait for services to spin up.
gosu azuracast php /var/azuracast/www/bin/uptime_wait || exit 1
# Initialize before running FPM
gosu azuracast azuracast_cli azuracast:setup:initialize || exit 1
# Run PHP-FPM
exec /usr/sbin/php-fpm${PHP_VERSION} -F --fpm-config /etc/php/${PHP_VERSION}/fpm/php-fpm.conf -c /etc/php/${PHP_VERSION}/fpm/

View File

@ -0,0 +1,5 @@
#!/bin/bash
sv -w 30 check php-fpm || exit 1
exec gosu azuracast php /var/azuracast/www/bin/console azuracast:sync:nowplaying

View File

@ -0,0 +1,5 @@
#!/bin/bash
sv -w 30 check php-fpm || exit 1
exec gosu azuracast php /var/azuracast/www/bin/console queue:process --worker-name=app_worker_0

View File

@ -3,24 +3,18 @@ set -e
source /bd_build/buildconfig
set -x
apt-get update
# Install common scripts
cp -rT /bd_build/scripts/ /usr/local/bin
chmod -R a+x /usr/local/bin
cp -rT /bd_build/web/scripts/ /usr/local/bin
# Install runit
$minimal_apt_get_install runit
cp -rT /bd_build/web/startup_scripts/. /etc/my_init.d/
# Install runit scripts
cp -rT /bd_build/startup_scripts/. /etc/my_init.d/
cp -rT /bd_build/runit/. /etc/service/
# cp -rT /bd_build/web/service.minimal/. /etc/service.minimal/
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
cp -rT /bd_build/web/service.full/. /etc/service.full/
# Run service setup for all setup scripts
for f in /bd_build/setup/*.sh; do
for f in /bd_build/web/setup/*.sh; do
bash "$f" -H
done
done

View File

@ -18,5 +18,5 @@ 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/
cp -r /bd_build/web/cron/. /etc/cron.d/
chmod -R 600 /etc/cron.d/*

View File

@ -6,9 +6,9 @@ set -x
$minimal_apt_get_install nginx nginx-common nginx-extras openssl
# Install nginx and configuration
cp /bd_build/nginx/proxy_params.conf /etc/nginx/proxy_params
cp /bd_build/nginx/nginx.conf.tmpl /etc/nginx/nginx.conf.tmpl
cp /bd_build/nginx/azuracast.conf.tmpl /etc/nginx/azuracast.conf.tmpl
cp /bd_build/web/nginx/proxy_params.conf /etc/nginx/proxy_params
cp /bd_build/web/nginx/nginx.conf.tmpl /etc/nginx/nginx.conf.tmpl
cp /bd_build/web/nginx/azuracast.conf.tmpl /etc/nginx/azuracast.conf.tmpl
mkdir -p /etc/nginx/azuracast.conf.d/

View File

@ -5,6 +5,8 @@ set -x
PHP_VERSION=8.1
echo "${PHP_VERSION}" >> /etc/container_environment/PHP_VERSION
add-apt-repository -y ppa:ondrej/php
apt-get update
@ -18,14 +20,19 @@ $minimal_apt_get_install php${PHP_VERSION}-fpm php${PHP_VERSION}-cli php${PHP_VE
mkdir -p /run/php
touch /run/php/php${PHP_VERSION}-fpm.pid
echo "PHP_VERSION=${PHP_VERSION}" >>/etc/php/.version
cp /bd_build/php/php.ini.tmpl /etc/php/${PHP_VERSION}/fpm/05-azuracast.ini.tmpl
cp /bd_build/php/www.conf.tmpl /etc/php/${PHP_VERSION}/fpm/www.conf.tmpl
cp /bd_build/web/php/php.ini.tmpl /etc/php/${PHP_VERSION}/fpm/05-azuracast.ini.tmpl
cp /bd_build/web/php/www.conf.tmpl /etc/php/${PHP_VERSION}/fpm/www.conf.tmpl
# Install Composer
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer
# Download PHP-FPM healthcheck script
$minimal_apt_get_install libfcgi-bin
wget -O /usr/local/bin/php-fpm-healthcheck \
https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/master/php-fpm-healthcheck \
&& chmod +x /usr/local/bin/php-fpm-healthcheck
# Install PHP SPX profiler
$minimal_apt_get_install php${PHP_VERSION}-dev zlib1g-dev build-essential

View File

@ -10,7 +10,7 @@ $minimal_apt_get_install sftpgo
mkdir -p /var/azuracast/sftpgo/persist /var/azuracast/sftpgo/backups
cp /bd_build/sftpgo/sftpgo.json /var/azuracast/sftpgo/sftpgo.json
cp /bd_build/web/sftpgo/sftpgo.json /var/azuracast/sftpgo/sftpgo.json
touch /var/azuracast/sftpgo/sftpgo.db
chown -R azuracast:azuracast /var/azuracast/sftpgo

View File

@ -1,8 +0,0 @@
#!/bin/bash
# Copy the php.ini template to its destination.
source /etc/php/.version
dockerize -template "/etc/php/${PHP_VERSION}/fpm/05-azuracast.ini.tmpl:/etc/php/${PHP_VERSION}/fpm/conf.d/05-azuracast.ini" \
-template "/etc/php/${PHP_VERSION}/fpm/www.conf.tmpl:/etc/php/${PHP_VERSION}/fpm/pool.d/www.conf" \
cp /etc/php/${PHP_VERSION}/fpm/conf.d/05-azuracast.ini /etc/php/${PHP_VERSION}/cli/conf.d/05-azuracast.ini

View File

@ -1,5 +0,0 @@
#!/bin/bash
set -ex
exec sudo -E -u azuracast azuracast_cli azuracast:setup:initialize