#!/usr/bin/env bash # shellcheck disable=SC2145,SC2178,SC2120,SC2162 # Functions to manage .env files __dotenv= __dotenv_file= __dotenv_cmd=.env .env() { REPLY=() [[ $__dotenv_file || ${1-} == -* ]] || .env.--file .env || return if declare -F -- ".env.${1-}" >/dev/null; then .env."$@" return fi return 64 } .env.-f() { .env.--file "$@"; } .env.get() { .env::arg "get requires a key" "$@" && [[ "$__dotenv" =~ ^(.*(^|$'\n'))([ ]*)"$1="(.*)$ ]] && REPLY=${BASH_REMATCH[4]%%$'\n'*} && REPLY=${REPLY%"${REPLY##*[![:space:]]}"} } .env.parse() { local line key while IFS= read -r line; do line=${line#"${line%%[![:space:]]*}"} # trim leading whitespace line=${line%"${line##*[![:space:]]}"} # trim trailing whitespace if [[ ! "$line" || "$line" == '#'* ]]; then continue; fi if (($#)); then for key; do if [[ $key == "${line%%=*}" ]]; then REPLY+=("$line") break fi done else REPLY+=("$line") fi done <<<"$__dotenv" ((${#REPLY[@]})) } .env.export() { ! .env.parse "$@" || export "${REPLY[@]}"; } .env.set() { .env::file load || return local key saved=$__dotenv while (($#)); do key=${1#+} key=${key%%=*} if .env.get "$key"; then REPLY=() if [[ $1 == +* ]]; then shift continue # skip if already found elif [[ $1 == *=* ]]; then __dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[3]}$1$'\n'${BASH_REMATCH[4]#*$'\n'} else __dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[4]#*$'\n'} continue # delete all occurrences fi elif [[ $1 == *=* ]]; then __dotenv+="${1#+}"$'\n' fi shift done [[ $__dotenv == "$saved" ]] || .env::file save } .env.puts() { echo "${1-}" >>"$__dotenv_file" && __dotenv+="$1"$'\n'; } .env.generate() { .env::arg "key required for generate" "$@" || return .env.get "$1" && return || REPLY=$("${@:2}") || return .env::one "generate: ouptut of '${*:2}' has more than one line" "$REPLY" || return .env.puts "$1=$REPLY" } .env.--file() { .env::arg "filename required for --file" "$@" || return __dotenv_file=$1 .env::file load || return (($# < 2)) || .env "${@:2}" } .env::arg() { [[ "${2-}" ]] || { echo "$__dotenv_cmd: $1" >&2 return 64 }; } .env::one() { [[ "$2" != *$'\n'* ]] || .env::arg "$1"; } .env::file() { local REPLY=$__dotenv_file case "$1" in load) __dotenv= ! [[ -f "$REPLY" ]] || __dotenv="$(<"$REPLY")"$'\n' || return ;; save) if [[ -L "$REPLY" ]] && declare -F -- realpath.resolved >/dev/null; then realpath.resolved "$REPLY" fi { [[ ! -f "$REPLY" ]] || cp -p "$REPLY" "$REPLY.bak"; } && printf %s "$__dotenv" >"$REPLY.bak" && mv "$REPLY.bak" "$REPLY" ;; esac } # Shortcut to convert semver version (x.yyy.zzz) into a comparable number. 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. ask() { # https://djm.me/ask local prompt default reply while true; do if [[ "${2:-}" == "Y" ]]; then prompt="Y/n" default=Y elif [[ "${2:-}" == "N" ]]; then prompt="y/N" default=N else prompt="y/n" default= fi # Ask the question (not using "read -p" as it uses stderr not stdout) echo -n "$1 [$prompt] " read reply # Default? if [[ -z "$reply" ]]; then reply=${default} fi # Check if the reply is valid case "$reply" in Y* | y*) return 0 ;; N* | n*) return 1 ;; esac done } # Generate a prompt to set an environment file value. envfile-set() { local VALUE INPUT .env --file .env .env get "$1" VALUE=${REPLY:-$2} echo -n "$3 [$VALUE]: " read INPUT VALUE=${INPUT:-$VALUE} .env set "${1}=${VALUE}" } # # Configure the ports used by AzuraCast. # setup-ports() { envfile-set "AZURACAST_HTTP_PORT" "80" "Port to use for HTTP connections" envfile-set "AZURACAST_HTTPS_PORT" "443" "Port to use for HTTPS connections" envfile-set "AZURACAST_SFTP_PORT" "2022" "Port to use for SFTP connections" } # # Configure release mode settings. # setup-release() { if [[ ! -f .env ]]; then curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/main/sample.env -o .env fi local OLD_RELEASE_CHANNEL .env --file .env get AZURACAST_VERSION OLD_RELEASE_CHANNEL="${REPLY:-latest}" local AZURACAST_VERSION="${OLD_RELEASE_CHANNEL}" if [[ $AZURACAST_VERSION == "latest" ]]; then if ask "Your current release channel is 'Rolling Release'. Switch to 'Stable' release channel?" N; then AZURACAST_VERSION="stable" fi elif [[ $AZURACAST_VERSION == "stable" ]]; then if ask "Your current release channel is 'Stable'. Switch to 'Rolling Release' release channel?" N; then AZURACAST_VERSION="latest" fi fi .env --file .env set AZURACAST_VERSION=${AZURACAST_VERSION} if [[ $AZURACAST_VERSION != $OLD_RELEASE_CHANNEL ]]; then if ask "You should update the Docker Utility Script after changing release channels. Automatically update it now?" Y; then update-self fi fi } check-install-requirements() { local CURRENT_OS CURRENT_ARCH REQUIRED_COMMANDS SCRIPT_DIR set -e echo "Checking installation requirements for AzuraCast..." CURRENT_OS=$(uname -s) if [[ $CURRENT_OS == "Linux" ]]; then echo -en "\e[32m[PASS]\e[0m Operating System: ${CURRENT_OS}\n" else echo -en "\e[41m[FAIL]\e[0m Operating System: ${CURRENT_OS}\n" echo " You are running an unsupported operating system." echo " Automated AzuraCast installation is not currently supported on this" echo " operating system." exit 1 fi CURRENT_ARCH=$(uname -m) if [[ $CURRENT_ARCH == "x86_64" ]]; then echo -en "\e[32m[PASS]\e[0m Architecture: ${CURRENT_ARCH}\n" elif [[ $CURRENT_ARCH == "aarch64" ]]; then echo -en "\e[32m[PASS]\e[0m Architecture: ${CURRENT_ARCH}\n" else echo -en "\e[41m[FAIL]\e[0m Architecture: ${CURRENT_ARCH}\n" echo " You are running an unsupported processor architecture." echo " Automated AzuraCast installation is not currently supported on this " echo " operating system." exit 1 fi REQUIRED_COMMANDS=(curl awk) for COMMAND in "${REQUIRED_COMMANDS[@]}" ; do if [[ $(command -v "$COMMAND") ]]; then echo -en "\e[32m[PASS]\e[0m Command Present: ${COMMAND}\n" else echo -en "\e[41m[FAIL]\e[0m Command Present: ${COMMAND}\n" echo " ${COMMAND} does not appear to be installed." echo " Install ${COMMAND} using your host's package manager," echo " then continue installing using this script." exit 1 fi done if [[ $EUID -ne 0 ]]; then if [[ $(command -v sudo) ]]; then echo -en "\e[32m[PASS]\e[0m User Permissions\n" else echo -en "\e[41m[FAIL]\e[0m User Permissions\n" echo " You are not currently the root user, and " echo " 'sudo' does not appear to be installed." echo " Install sudo using your host's package manager," echo " then continue installing using this script." exit 1 fi else echo -en "\e[32m[PASS]\e[0m User Permissions\n" fi SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" if [[ $SCRIPT_DIR == "/var/azuracast" ]]; then echo -en "\e[32m[PASS]\e[0m Installation Directory\n" else echo -en "\e[93m[WARN]\e[0m Installation Directory\n" echo " AzuraCast is not installed in /var/azuracast, as is recommended" echo " for most installations. This will not prevent AzuraCast from" echo " working, but you will need to update any instructions in our" echo " documentation to reflect your current directory:" echo " $SCRIPT_DIR" fi echo -en "\e[32m[PASS]\e[0m All requirements met!\n" set +e } install-docker() { set -e curl -fsSL get.docker.com -o get-docker.sh sh get-docker.sh rm get-docker.sh if [[ $EUID -ne 0 ]]; then sudo usermod -aG docker "$(whoami)" echo "You must log out or restart to apply necessary Docker permissions changes." echo "Restart, then continue installing using this script." exit 1 fi set +e } install-docker-compose() { set -e echo "Installing Docker Compose..." curl -fsSL -o docker-compose https://github.com/docker/compose/releases/download/v2.4.1/docker-compose-linux-$(uname -m) ARCHITECTURE=amd64 if [ "$(uname -m)" = "aarch64" ]; then ARCHITECTURE=arm64 fi curl -fsSL -o docker-compose-switch https://github.com/docker/compose-switch/releases/download/v1.0.4/docker-compose-linux-${ARCHITECTURE} if [[ $EUID -ne 0 ]]; then sudo chmod a+x ./docker-compose sudo chmod a+x ./docker-compose-switch sudo mv ./docker-compose /usr/libexec/docker/cli-plugins/docker-compose sudo mv ./docker-compose-switch /usr/local/bin/docker-compose else chmod a+x ./docker-compose chmod a+x ./docker-compose-switch mv ./docker-compose /usr/libexec/docker/cli-plugins/docker-compose mv ./docker-compose-switch /usr/local/bin/docker-compose fi echo "Docker Compose updated!" set +e } run-installer() { local AZURACAST_RELEASE_BRANCH AZURACAST_RELEASE_BRANCH=$(get-release-branch-name) if [[ ! -f .env ]]; then curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/$AZURACAST_RELEASE_BRANCH/sample.env -o .env fi if [[ ! -f azuracast.env ]]; then curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/$AZURACAST_RELEASE_BRANCH/azuracast.sample.env -o azuracast.env fi if [[ ! -f docker-compose.yml ]]; then curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/$AZURACAST_RELEASE_BRANCH/docker-compose.sample.yml -o docker-compose.yml fi touch docker-compose.new.yml local dc_config_test=$(docker-compose -f docker-compose.new.yml config 2>/dev/null) if [ $? -ne 0 ]; then if ask "Docker Compose needs to be updated to continue. Update to latest version?" Y; then install-docker-compose fi 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 "$@" rm docker-compose.installer.yml } # # Run the initial installer of Docker and AzuraCast. # Usage: ./docker.sh install # install() { check-install-requirements if [[ $(command -v docker) && $(docker --version) ]]; then echo "Docker is already installed! Continuing..." else if ask "Docker does not appear to be installed. Install Docker now?" Y; then install-docker fi fi if [[ $(command -v docker-compose) ]]; then echo "Docker Compose is already installed. Continuing..." else if ask "Docker Compose does not appear to be installed. Install Docker Compose now?" Y; then install-docker-compose fi fi setup-release run-installer "$@" # Installer creates a file at docker-compose.new.yml; copy it to the main spot. if [[ -s docker-compose.new.yml ]]; then if [[ -f docker-compose.yml ]]; then rm docker-compose.yml fi mv docker-compose.new.yml docker-compose.yml fi # 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 docker-compose run --rm web -- azuracast_install "$@" docker-compose up -d exit } install-dev() { if [[ $(command -v docker) && $(docker --version) ]]; then echo "Docker is already installed! Continuing..." else if ask "Docker does not appear to be installed. Install Docker now?" Y; then install-docker fi fi if [[ $(command -v docker-compose) ]]; then echo "Docker Compose is already installed. Continuing..." else if ask "Docker Compose does not appear to be installed. Install Docker Compose now?" Y; then install-docker-compose fi fi if [[ ! -f docker-compose.yml ]]; then cp docker-compose.sample.yml docker-compose.yml fi if [[ ! -f docker-compose.override.yml ]]; then cp docker-compose.dev.yml docker-compose.override.yml fi if [[ ! -f .env ]]; then cp dev.env .env fi if [[ ! -f azuracast.env ]]; then cp azuracast.dev.env azuracast.env echo "Customize azuracast.env file now before continuing. Re-run this command to continue installation." exit fi # 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 chmod 777 ./frontend/ ./web/ ./vendor/ \ ./web/static/ ./web/static/api/ \ ./web/static/dist/ ./web/static/img/ docker-compose build docker-compose run --rm web -- azuracast_install "$@" docker-compose -p azuracast_frontend -f docker-compose.frontend.yml build docker-compose -p azuracast_frontend -f docker-compose.frontend.yml run --rm frontend npm run build docker-compose up -d exit } # # Update the Docker images and codebase. # Usage: ./docker.sh update # update() { echo "[NOTICE] Before you continue, please make sure you have a recent snapshot of your system and or backed it up." if ask "Are you ready to continue with the update?" Y; then # Check for a new Docker Utility Script. 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 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 fi fi if [[ ${UPDATE_UTILITY} -ne 0 ]]; then mv docker.new.sh docker.sh chmod a+x docker.sh echo "A new Docker Utility Script has been downloaded." echo "Please re-run the update process to continue." exit else rm docker.new.sh fi # Check Docker version. DOCKER_VERSION=$(docker version -f "{{.Server.Version}}") DOCKER_VERSION_MAJOR=$(echo "$DOCKER_VERSION"| cut -d'.' -f 1) if [ "${DOCKER_VERSION_MAJOR}" -ge 20 ]; then echo "Docker server (version ${DOCKER_VERSION}) meets minimum version requirements." else if ask "Docker is out of date on this server. Attempt automatic upgrade?" Y; then install-docker install-docker-compose fi fi run-installer --update "$@" # Check for updated Docker Compose config. local COMPOSE_FILES_MATCH if [[ ! -s docker-compose.new.yml ]]; then curl -fsSL https://raw.githubusercontent.com/AzuraCast/AzuraCast/$AZURACAST_RELEASE_BRANCH/docker-compose.sample.yml -o docker-compose.new.yml fi COMPOSE_FILES_MATCH="$( cmp --silent docker-compose.yml docker-compose.new.yml echo $? )" if [[ ${COMPOSE_FILES_MATCH} -ne 0 ]]; then docker-compose -f docker-compose.new.yml pull docker-compose down --timeout 30 cp docker-compose.yml docker-compose.backup.yml mv docker-compose.new.yml docker-compose.yml else rm docker-compose.new.yml docker-compose pull docker-compose down --timeout 30 fi 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 docker system prune -f fi echo "Update complete!" fi exit } # # Update this Docker utility script. # Usage: ./docker.sh update-self # update-self() { 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." exit } # # Run a CLI command inside the Docker container. # Usage: ./docker.sh cli [command] # cli() { docker-compose exec --user="azuracast" web azuracast_cli "$@" exit } # # Enter the bash terminal of the running web container. # Usage: ./docker.sh bash # bash() { docker-compose exec --user="azuracast" web bash exit } # # Enter the MariaDB database management terminal with the correct credentials. # db() { local MYSQL_HOST MYSQL_PORT MYSQL_USER MYSQL_PASSWORD MYSQL_DATABASE .env --file azuracast.env get MYSQL_HOST MYSQL_HOST="${REPLY:-localhost}" .env --file azuracast.env get MYSQL_PORT MYSQL_PORT="${REPLY:-3306}" .env --file azuracast.env get MYSQL_USER MYSQL_USER="${REPLY:-azuracast}" .env --file azuracast.env get MYSQL_PASSWORD MYSQL_PASSWORD="${REPLY:-azur4c457}" .env --file azuracast.env get MYSQL_DATABASE MYSQL_DATABASE="${REPLY:-azuracast}" docker-compose exec --user="mysql" web mysql --user=${MYSQL_USER} --password=${MYSQL_PASSWORD} \ --host=${MYSQL_HOST} --port=${MYSQL_PORT} --database=${MYSQL_DATABASE} exit } # # Back up the Docker volumes to a .tar.gz file. # Usage: # ./docker.sh backup [/custom/backup/dir/custombackupname.zip] # backup() { local BACKUP_PATH BACKUP_DIR BACKUP_FILENAME BACKUP_EXT BACKUP_PATH=$(readlink -f ${1:-"./backup.tar.gz"}) BACKUP_DIR=$(dirname -- "$BACKUP_PATH") BACKUP_FILENAME=$(basename -- "$BACKUP_PATH") BACKUP_EXT="${BACKUP_FILENAME##*.}" shift # Prepare permissions if [[ $EUID -ne 0 ]]; then .env --file .env set AZURACAST_PUID="$(id -u)" .env --file .env set AZURACAST_PGID="$(id -g)" fi docker-compose exec --user="azuracast" 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" \ -v "$BACKUP_DIR:/backup_dest" \ busybox mv "/backup_src/${BACKUP_FILENAME}" "/backup_dest/${BACKUP_FILENAME}" } # # Restore an AzuraCast backup into Docker. # Usage: # ./docker.sh restore [/custom/backup/dir/custombackupname.zip] # restore() { if [[ ! -f .env ]] || [[ ! -f azuracast.env ]]; then echo "AzuraCast hasn't been installed yet on this server." echo "You should run './docker.sh install' first before restoring." exit 1 fi if ask "Restoring will remove any existing AzuraCast installation data, replacing it with your backup. Continue?" Y; then if [[ $1 != "" ]]; then local BACKUP_PATH BACKUP_DIR BACKUP_FILENAME BACKUP_EXT BACKUP_PATH=$(readlink -f ${1:-"./backup.tar.gz"}) BACKUP_DIR=$(dirname -- "$BACKUP_PATH") BACKUP_FILENAME=$(basename -- "$BACKUP_PATH") BACKUP_EXT="${BACKUP_FILENAME##*.}" shift if [[ ! -f ${BACKUP_PATH} ]]; then echo "File '${BACKUP_PATH}' does not exist. Nothing to restore." exit 1 fi docker-compose down # Remove most AzuraCast volumes but preserve some essential ones. docker volume rm -f $(docker volume ls | grep 'azuracast' | grep -v 'station\|install' | awk 'NR>1 {print $2}') docker volume create azuracast_backups # Move from local filesystem to Docker volume docker run --rm -v "$BACKUP_DIR:/backup_src" \ -v "azuracast_backups:/backup_dest" \ busybox mv "/backup_src/${BACKUP_FILENAME}" "/backup_dest/${BACKUP_FILENAME}" # Prepare permissions if [[ $EUID -ne 0 ]]; then .env --file .env set AZURACAST_PUID="$(id -u)" .env --file .env set AZURACAST_PGID="$(id -g)" fi docker-compose run --rm web -- azuracast_restore "/var/azuracast/backups/${BACKUP_FILENAME}" "$@" # Move file back from volume to local filesystem docker run --rm -v "azuracast_backups:/backup_src" \ -v "$BACKUP_DIR:/backup_dest" \ busybox mv "/backup_src/${BACKUP_FILENAME}" "/backup_dest/${BACKUP_FILENAME}" docker-compose down --timeout 30 docker-compose up -d else docker-compose down # Remove most AzuraCast volumes but preserve some essential ones. docker volume rm -f $(docker volume ls | grep 'azuracast' | grep -v 'station\|backups\|install' | awk 'NR>1 {print $2}') docker-compose run --rm web -- azuracast_restore "$@" docker-compose down --timeout 30 docker-compose up -d fi fi exit } # # Restore the Docker volumes from a legacy backup format .tar.gz file. # Usage: # ./docker.sh restore [/custom/backup/dir/custombackupname.tar.gz] # restore-legacy() { local APP_BASE_DIR BACKUP_PATH BACKUP_DIR BACKUP_FILENAME APP_BASE_DIR=$(pwd) BACKUP_PATH=${1:-"./backup.tar.gz"} BACKUP_DIR=$(cd "$(dirname "$BACKUP_PATH")" && pwd) BACKUP_FILENAME=$(basename "$BACKUP_PATH") cd "$APP_BASE_DIR" || exit if [ -f "$BACKUP_PATH" ]; then docker-compose down --timeout 30 docker volume rm azuracast_db_data azuracast_station_data docker volume create azuracast_db_data docker volume create azuracast_station_data docker run --rm -v "$BACKUP_DIR:/backup" \ -v azuracast_db_data:/azuracast/db \ -v azuracast_station_data:/azuracast/stations \ busybox tar zxvf "/backup/$BACKUP_FILENAME" docker-compose up -d else echo "File $BACKUP_PATH does not exist in this directory. Nothing to restore." exit 1 fi exit } # # DEVELOPER TOOL: # Access the static console as a developer. # Usage: ./docker.sh static [static_container_command] # static() { docker-compose -f docker-compose.frontend.yml down -v docker-compose -f docker-compose.frontend.yml build docker-compose --env-file=.env -f docker-compose.frontend.yml run --rm frontend "$@" exit } # # Stop all Docker containers and remove related volumes. # Usage: ./docker.sh uninstall # uninstall() { if ask "This operation is destructive and will wipe your existing Docker containers. Continue?" N; then docker-compose down -v docker-compose rm -f docker volume prune -f echo "All AzuraCast Docker containers and volumes were removed." echo "To remove *all* Docker containers and volumes, run:" echo " docker stop \$(docker ps -a -q)" echo " docker rm \$(docker ps -a -q)" echo " docker volume prune -f" echo "" fi exit } # # LetsEncrypt: Now managed via the Web UI. # setup-letsencrypt() { echo "LetsEncrypt is now managed from within the web interface." echo "You can manage it via the 'Administration' panel, then 'System Settings'." echo "Under 'Services' you will find the LetsEncrypt settings." } letsencrypt-create() { setup-letsencrypt exit } # # Utility script to facilitate switching ports. # Usage: ./docker.sh change-ports # change-ports() { setup-ports docker-compose down --timeout 30 docker-compose up -d } # # Helper scripts for basic Docker Compose functions # up() { echo "Starting up AzuraCast services..." docker-compose up -d } down() { echo "Shutting down AzuraCast services..." docker-compose down --timeout 30 } restart() { down up } # Ensure we're in the same directory as this script. cd "$( dirname "${BASH_SOURCE[0]}" )" || exit "$@"