diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml index 1ad022a..6d4be62 100644 --- a/.github/workflows/docker-deploy.yml +++ b/.github/workflows/docker-deploy.yml @@ -44,7 +44,7 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Log in to Docker Hub - if: env.SHOULD_BUILD == 'true' + if: ${{ env.SHOULD_BUILD == 'true' }} uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} diff --git a/.gitignore b/.gitignore index 59e1534..d479b72 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ data/db.sqlite3 data/django_key.txt .env .pytest_cache -filetree.txt \ No newline at end of file +filetree.txt +.vscode/ diff --git a/README.md b/README.md index e0a9527..3d1d837 100644 --- a/README.md +++ b/README.md @@ -25,30 +25,67 @@ As this application is intended to interface with [Sonarr](https://sonarr.tv) an ### Linux (Fully Automated) -```bash -curl https://raw.githubusercontent.com/nickheyer/DiscoFlix/main/auto_install_update.sh -o auto_install_update.sh && sudo bash auto_install_update.sh +```bash +wget https://raw.githubusercontent.com/nickheyer/DiscoFlix/main/auto_install_update.sh +sudo ./auto_install_update.sh ``` -### Other Operating Systems (Windows, Mac, etc.) +### Installation Using Docker CLI (All Operating Systems) + +#### Download Docker Image -##### Download Docker Image (Recommended - x86_64 Architecture) +To get the latest version of Discoflix, run: ```bash docker image pull nickheyer/discoflix:latest ``` -##### OR Download Docker Image (ARM64, aarch64 Architecture, ie: Raspberry-Pi, Mac M1, etc.) +#### Run Docker Container + +To run Discoflix in a container, use the following command. Make sure to mount a volume to `/app/data` in the container so that your data persists across container restarts. Here's how to do it: + +**Example**: ```bash -docker image pull nickheyer/discoflix_rpi:latest +docker run -d -p 5454:5454 --name discoflix -v /opt/discoflix/data:/app/data nickheyer/discoflix ``` -##### Run Docker Container + +**On Other Platforms**: +You can replace `/opt/discoflix/data` with an appropriate path on your local system. For Windows and macOS, specify the full path you want to use for data storage. +This allows DiscoFlix's database to remain on your pc even if you delete or update your container! + +##### The server within the docker container can be accessed locally at [http://127.0.0.1:5454](http://127.0.0.1:5454) + + +### Installation Using Docker-Compose + +#### Prep + +First, get the compose file: ```bash -docker run -d -p 5454:5454 --name discoflix nickheyer/discoflix +mkdir -p /opt/discoflix/data && cd /opt/discoflix +wget https://raw.githubusercontent.com/nickheyer/DiscoFlix/main/docker-compose.yml ``` + +#### Start the Container Using Docker Compose + +With Docker Compose installed on your system, you can start Discoflix by running: + +```bash +docker-compose -f /opt/discoflix/docker-compose.yml up -d +``` + +This will pull the latest Discoflix image and run it, mounting the volume specified in the `docker-compose.yml` file. This will use the default /opt/discoflix/data, so modify this path if needed. + ##### The server within the docker container can be accessed locally at [http://127.0.0.1:5454](http://127.0.0.1:5454) +**To stop the service**, use: + +```bash +docker-compose -f /opt/discoflix/docker-compose.yml down +``` +

diff --git a/auto_install_update.sh b/auto_install_update.sh old mode 100644 new mode 100755 index fe9bfe8..50182a0 --- a/auto_install_update.sh +++ b/auto_install_update.sh @@ -1,327 +1,126 @@ #!/bin/bash +set -e -#App-Agnostic-Auto-Install-&-Update-Script created by Nicholas Heyer - -# .... SCRIPT CONFIGURATION .... - -app_name="DiscoFlix" # <-- Required! -#database_file="/data/disco.db" # <-- Optional -docker_user="nickheyer" # <-- Required! -docker_app="discoflix" # <-- Required! -ports="5454:5454" # <-- Optional - - -#Checking if script was run as root (with sudo) -if [ "$EUID" -ne 0 ] - then echo "This script requires root access. Please try again using \"sudo bash $0\"" - exit -fi - -# .... Creating TMP dir for backup and install files .... -tmp_dir=$(mktemp -d -t ci-XXXXXXXXXX) - -#Determine system architecture -system_arch=$(uname -i) - -#Appending repo based on system arch -[[ $system_arch =~ ^arm ]] && docker_app="${docker_app}_rpi" +# Universal Install & Update Script for DiscoFlix by Nicholas Heyer +# USE ENV VARIABLE IF PROVIDED, OTHERWISE USE SCRIPT DEFAULTS +app_name="${APP_NAME:-DiscoFlix}" +docker_user="${DOCKER_USER:-nickheyer}" +docker_app="${DOCKER_APP:-discoflix}" docker_repo="${docker_user}/${docker_app}" - -#Greet user - -echo "Initializing ${app_name} Install & Update Script..." - -#Detect if Docker is installed - -echo "Checking for Docker installation on current machine..." - -docker_loc="$(command -v docker)" - -if [ ! -z "${docker_loc}" ] -then - - echo "Docker is installed, install location saved for future reference..." - -else - - while true; do - read -p "Docker installation not detected on this machine. Would you like to install Docker (required for ${app_name})? " yn - case $yn in - - [Yy]* ) - echo "Installing Docker via get-docker.com --- Please wait..." - iscript_loc="${tmp_dir}/get-docker.sh" - curl -fsSL https://get.docker.com -o $iscript_loc - sh $iscript_loc - break - ;; - - [Nn]* ) - echo "Docker is required for installation of ${app_name}. Please install before retrying this installation. Thank you!" - exit - ;; - - * ) echo "Please answer yes or no";; - esac - done +outer_port="${DOCKER_PORT_HOST:-5454}" +inner_port="${DOCKER_PORT_CONTAINER:-5454}" +ports="${outer_port}:${inner_port}" +host_volume_dir="${HOST_VOLUME_DIR:-/opt/discoflix/data}" +container_data_path="${CONTAINER_DATA_PATH:-/app/data}" +volume_path="${host_volume_dir}:${container_data_path}" +service_file="/etc/systemd/system/docker-${docker_app}.service" +create_service="${CREATE_SERVICE:-false}" + +# CHECK ROOT +if [ "$EUID" -ne 0 ]; then + echo "This script requires root access. Please run with sudo: sudo bash $0" + exit 1 fi -#Checking for docker-.service. If service is running, stop service (which stops container). - -echo "Handling service file creation and control. Please wait..." - -app_service_file="/etc/systemd/system/docker-${docker_app}.service" - -echo "Checking if .service file exists at ${app_service_file}. Please wait..." -if [ -f "${app_service_file}" ] -then - - echo "Service exists! Stopping any running services associated with ${app_name}. Please wait..." - systemctl stop "docker-${docker_app}.service" - -else - - docker_loc="$(command -v docker)" - - echo "No services exist for this application. Creating service file. Please wait..." - echo "[Unit] - Description=${app_name} Service - Requires=docker.service - After=docker.service - - [Service] - Restart=always - ExecStart=${docker_loc} start -a ${docker_app} - ExecStop=${docker_loc} stop -t 2 ${docker_app} - - [Install] - WantedBy=default.target" > "${app_service_file}" +# CHECK FOR DOCKER, INSTALL IF NEEDED +which docker > /dev/null || { + read -p "Docker is not installed. Would you like to install Docker? (y/N): " ans + if [[ $ans != "y" ]]; then + echo "Installation aborted. Exiting." + exit 1 + fi + echo "Installing Docker..." + curl -fsSL https://get.docker.com -o get-docker.sh + sh get-docker.sh + rm get-docker.sh +} + +# CREATE SYSTEMD SERVICE IF SYSTEMD IS PRESENT AND CREATE_SERVICE IS TRUE +if command -v systemctl &> /dev/null && [ "$create_service" = "true" ]; then + if [ -f "$service_file" ]; then + echo "Stopping existing ${app_name} service..." + systemctl stop "docker-${docker_app}.service" + else + echo "Creating systemd service file for ${app_name}..." + cat << EOF > "$service_file" +[Unit] +Description=${app_name} Service +Requires=docker.service +After=docker.service + +[Service] +Restart=always +ExecStart=$(which docker) run -d -p "${ports}" -v "${volume_path}" --name "${docker_app}" "${docker_repo}" +ExecStop=$(which docker) stop ${docker_app} + +[Install] +WantedBy=multi-user.target +EOF + systemctl daemon-reload + fi fi -#Checking for running app containers. Stopping them. - -echo "Checking for pre-existing ${app_name} containers..." -container_state=$(docker container ls -a --filter name="${docker_app}" --format "{{.State}}") -container_id=$(docker container ls -a --filter name="${docker_app}" --format "{{.ID}}") - -[ -z "${container_id}" ] || echo "Container discovered: $container_id is $container_state." - -[ "${container_state}" = "running" ] && echo "Shutting down container. Please wait..." && docker container stop $docker_app #If container is running, stop container - - -#If app image exists, search for app in running containers. - -echo "Checking for ${app_name} images on current machine..." -app_installation=$(docker images "${docker_repo}:latest" --format "{{.Repository}}") - -if [ -z "${app_installation}" ] #If app image does not exist -then - - echo "No installations of ${app_name} detected. Pulling new image from ${docker_repo}. Please wait..." - docker pull "${docker_repo}" - -else - - while true; do - read -p "Installation of ${app_name} detected. Would you like to force an update to the most recent version? " yn - case $yn in - - [Yy]* ) - echo "Pulling most recent image for ${docker_repo}. Please wait..." - docker pull "${docker_repo}" - break - ;; - - [Nn]* ) - echo "Skipping update..." - break - ;; - - * ) echo "Please answer yes or no";; - esac - done +# MAKE SURE VOL MOUNT DIR EXISTS +if [ ! -d "$host_volume_dir" ]; then + echo "Creating volume directory at $host_volume_dir..." + mkdir -p "$host_volume_dir" + chmod 777 "$host_volume_dir" fi -updated_installation=$(docker images "${docker_repo}:latest") - - - -#If app container already exists, backup all configs, data, etc. - -if [ -z "${container_id}" ] -then - - echo "No containers detected. Skipping file backup..." - -else - - tmp_img="${docker_app}_tmp" - - echo "Creating temporary image from stopped container..." - docker commit $container_id $tmp_img - - echo "Running temporary container from temp image..." - docker run -d --name $tmp_img $tmp_img - tmp_id=$(docker container ls -a --filter name="${tmp_img}" --format "{{.ID}}") - - db_backup_successful=false - if [ -v database_file ]; then - read -p "Would you like to backup the database? (y/N): " answer - if [[ $answer =~ ^[Yy] ]]; then - echo "Preparing to backup the database..." - - # Check if SQLite is installed - if ! command -v sqlite3 &> /dev/null; then - echo "SQLite is not installed. Attempting to install SQLite..." - - # Detect the platform and install SQLite - case "$(uname -s)" in - Linux) - if [ -f "/etc/os-release" ]; then - . /etc/os-release - case "$ID" in - debian|ubuntu) - sudo apt-get update - sudo apt-get install sqlite3 libsqlite3-dev - ;; - fedora) - sudo dnf install sqlite sqlite-devel - ;; - centos|rhel) - sudo yum install sqlite sqlite-devel - ;; - *) - echo "Unsupported Linux distribution. Please install SQLite manually." - exit 1 - ;; - esac - else - echo "Unsupported Linux distribution. Please install SQLite manually." - exit 1 - fi - ;; - Darwin) - if command -v brew &> /dev/null; then - brew install sqlite - else - echo "Homebrew is not installed. Please install Homebrew and then install SQLite." - exit 1 - fi - ;; - *) - echo "Unsupported platform. Please install SQLite manually." - exit 1 - ;; - esac - fi - - # Continue with the backup process - echo "SQLite is installed. Proceeding with the backup..." - database_base_file=$(basename ${database_file}) - docker cp "${tmp_id}:/app${database_file}" "${tmp_dir}/${database_base_file}" - db_file_1="${tmp_dir}/${database_base_file}" - db_backup_successful=true - else - echo "Backup process canceled." - fi +# CHECK FOR EXISTING CONTAINER +if docker container inspect "$docker_app" &> /dev/null; then + echo "Existing ${app_name} container detected." + # CHECK FOR VOL MOUNT + mounts=$(docker inspect -f '{{ range .Mounts }}{{ .Destination }} {{ end }}' "$docker_app") + if [[ ! $mounts =~ $container_data_path ]]; then + echo "No volume mount detected for data. Backing up data from the running container..." + # COPY DATA FROM CONTAINER TO HOST MOUNT + docker cp "${docker_app}:${container_data_path}/." "$host_volume_dir" + echo "Data backup completed to ${host_volume_dir}." else - echo "No DB File Provided" + echo "Volume mount detected. No data backup needed." fi - echo "Cleaning up temporary image and containers..." - docker container rm -f $tmp_img - docker image rm -f $tmp_img - - #Removing old containers - echo "Removing old container #${container_id}" - docker container rm -f $container_id - + # STOP + RM CONTAINER + echo "Stopping and removing the existing ${app_name} container..." + docker container stop "$docker_app" + docker container rm "$docker_app" fi +# PULL IMAGE +echo "Pulling the latest image for ${app_name}..." +docker pull "$docker_repo" -#Create container using new image +# RUN CONTAINER +echo "Starting ${app_name} container..." +docker run -d -p "${ports}" -v "${volume_path}" --name "${docker_app}" "${docker_repo}" -echo "Creating/running container from updated image. Please wait..." -[ -z $ports ] && docker run -d --name $docker_app $docker_repo || docker run -d -p $ports --name $docker_app $docker_repo -new_id=$(docker container ls -a --filter name="${docker_app}" --format "{{.ID}}") - -#Injecting backed-up files into new container +# START SYSTEMD SERVICE IF SYSTEMD IS PRESENT AND CREATE_SERVICE IS TRUE +if command -v systemctl &> /dev/null && [ "$create_service" = "true" ]; then + echo "Enabling and starting ${app_name} service..." + systemctl enable "docker-${docker_app}.service" + systemctl start "docker-${docker_app}.service" +fi +# VERIFY RUNNING CONTAINER +if [ "$(docker container inspect -f '{{.State.Running}}' "$docker_app")" == "true" ]; then + echo "${app_name} is successfully installed and running!" + ip=$(hostname -i | cut -f1 -d' ') || '127.0.1.1' + port=$(echo $outer_port) -if [ -z "${container_id}" ] -then + echo " " + echo "-----------------------------------------------------------------------------------------------" + echo " " - echo "No backed up files to inject, skipping container injection. Please wait..." + echo "${app_name} should be remotely accessible via http://${ip}:${port}" + echo " " + echo "-----------------------------------------------------------------------------------------------" + echo " " else - if $db_backup_successful; then - echo "Injecting DB" - docker exec ${docker_app} ls - database_base_file=$(basename ${database_file}) - docker cp "${new_id}:/app${database_file}" "${tmp_dir}/${base_file}_new" - db_file_2="${tmp_dir}/${base_file}_new" - cat $db_file_2 - - # Get schema from DB files - sqlite3 "$db_file_1" ".schema" | sort > "${tmp_dir}/schema_1.txt" - sqlite3 "$db_file_2" ".schema" | sort > "${tmp_dir}/schema_2.txt" - - if diff -q "${tmp_dir}/schema_1.txt" "${tmp_dir}/schema_2.txt" > /dev/null; then - echo "The schemas are compatible for backup." - docker cp "${tmp_dir}/${database_base_file}" "${new_id}:/app${database_file}" - else - echo "The schemas are not compatible for backup." - fi - fi + echo "Failed to start ${app_name} container. Please check Docker logs for details." + exit 1 fi - - -#Preparing for service start by shutting down new container - -echo "Temporarily shutting down ${docker_repo} (will restart with service)..." -docker container stop $docker_app - -#Reloading systemctl daemon to populate changes to services - -echo "Reloading systemctl daemon to poulate changes to services. Please wait..." -systemctl daemon-reload - -#Starting services - -echo "Starting docker-${docker_app}.service. Please wait..." -systemctl start "docker-${docker_app}.service" -wait - -#Cleaning tmp files -echo "Cleaning up temporary files and removing tmp_dir. Please wait..." -rm -rf $tmp_dir - -#Confirming container is running now as it should - -echo "Verifying that container is running properly. Please wait..." -new_state=$(docker container ls -a --filter name="${docker_app}" --format "{{.State}}") -while [ ! "${new_state}" = "running" ] -do - sleep 1 - echo "Service Status: ${new_state}" - new_state=$(docker container ls -a --filter name="${docker_app}" --format "{{.State}}") - [ "${new_state}" = "dead" ] && echo "Container is unable to start. Try running this install script again or try installing manually." && exit -done - -echo "${app_name} install/update successful. Container is running!" - -ip=$(hostname -I | cut -f1 -d' ') -port=$(echo $ports | cut -f1 -d':') - -echo " " -echo "-----------------------------------------------------------------------------------------------" -echo " " - -echo "${app_name} should be remotely accessible via http://${ip}:${port}" - -echo " " -echo "-----------------------------------------------------------------------------------------------" -echo " " \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..83a6912 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3.8' + +services: + discoflix: + image: nickheyer/discoflix:latest + container_name: discoflix + ports: + - "5454:5454" + volumes: + - /opt/discoflix/data:/app/data + restart: unless-stopped