diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..ec4b09e3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: docker + directory: "/" + schedule: + interval: daily + time: "11:00" + open-pull-requests-limit: 10 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..cfd3e396 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,89 @@ +name: Publish Docker image +on: + push: + branches: [ "dev", "master" ] + pull_request: + branches: [ "dev", "master" ] + # Publish `v1.2.3` tags as releases. + tags: + - v* + + # Run tests for any PRs. + # pull_request: + + # steps: +# - uses: actions/checkout@v2 + # Setup mysqlserver. + # See also https://docs.docker.com/docker-hub/builds/automated-testing/ +jobs: + mysql57: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Shutdown existing MySQL + run: sudo service mysql stop + + + push_to_registries: + #- name: Push Docker image to multiple registries + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Set up MySQL + uses: cweinberger/github-actions-mysql@main + with: + version: 5.7 + hostPort: 3306 + containerPort: 3306 + rootPassword: root + user: TestUser + password: TestPassword + database: TestDatabase + characterSet: utf8mb4 + collation: utf8mb4_general_ci + sqlMode: NO_ENGINE_SUBSTITUTION + + - name: Print running docker containers + run: docker ps + - name: Check out the repo + uses: actions/checkout@v3 + - name: Get branch name + run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV + - name: Identify git repo path + run: ls $GITHUB_WORKSPACE && ls -l /home/runner/work/ + - name: Wait for mysql + run: sleep 15 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Log in to Docker Hub + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + # username: curtishall + # password: ${{ secrets.DOCKER_PASSWORD }} +# - name: Extract metadata (tags, labels) for Docker +# id: meta +# uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 +# with: +# images: bluecherrydvr/bluecherry + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: bluecherrydvr/bluecherry + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + #working-directory: server/ + context: "{{defaultContext}}:actions" + # username: curtishall + # password: ${{ secrets.DOCKER_PASSWORD }} +# file: ./server/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/actions/Dockerfile b/actions/Dockerfile new file mode 100644 index 00000000..7caad5c5 --- /dev/null +++ b/actions/Dockerfile @@ -0,0 +1,216 @@ +# set a base image with environment to build from +FROM ubuntu:20.04 AS baseos + +ARG BLUECHERRY_GIT_BRANCH_TAG=master + +# --------------------------------------------------------------------------- +# Build the base OS with some development libs and tools +FROM baseos AS os_dev_environment +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /root + +RUN apt-get update + +RUN apt-get install --no-install-recommends -y \ + git sudo openssl ca-certificates wget gnupg gnupg2 gnupg1 \ + ssl-cert nmap curl sysstat iproute2 \ + autoconf automake libtool build-essential gcc g++ \ + debhelper ccache bison flex texinfo yasm cmake + +RUN apt-get install --no-install-recommends -y \ + libbsd-dev libopencv-dev libudev-dev libva-dev \ + linux-image-generic linux-headers-generic \ + libmysqlclient-dev rsyslog + +# --------------------------------------------------------------------------- +FROM os_dev_environment as bluecherry_base_environment +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /root + +# RUN git clone --recurse-submodules --progress --depth 1 \ + RUN git clone --recurse-submodules --progress http://github.com/bluecherrydvr/bluecherry-apps.git \ + && cd bluecherry-apps \ + && git checkout $BLUECHERRY_GIT_BRANCH_TAG \ + && sed -i 's/#define PRODUCT_VERSION "3.1.0-rc9"/#define PRODUCT_VERSION "3.1.0-rc9-docker"/' server/v3license_processor.h + + +RUN apt-get --no-install-recommends -y install \ + libbsd0 libc6 libgcc1 libssl1.1 libstdc++6 libudev1 \ + zlib1g ucf mkvtoolnix v4l-utils vainfo i965-va-driver + +RUN apt-get --no-install-recommends -y install \ + php-mail php-mail-mime php-net-smtp php-gd php-curl \ + php-mysql php-sqlite3 \ + apache2 libapache2-mod-php mysql-client sqlite3 + +# --------------------------------------------------------------------------- +# Build the bluecherry app and dependencies. This is done in a separate +# image because there are many ways it can fail and then we save time +# by being able to reuse prior containers leading up to this build. +FROM bluecherry_base_environment as bluecherry_build +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /root + +COPY depends/onvif_tool bluecherry-apps/utils/onvif_tool + +RUN cd bluecherry-apps \ + && ./scripts/build_pkg_native.sh + + +# --------------------------------------------------------------------------- +FROM bluecherry_build as bluecherry_build_cleaned +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /root + +RUN rm -rf /usr/src/linux-headers-* + +RUN rm -rf .ccache \ + && rm -rf bluecherry-apps/.git \ + && rm -rf bluecherry-apps/misc/libav \ + && rm -rf bluecherry-apps/misc/libconfig \ + && rm -rf bluecherry-apps/misc/pugixml + + +# --------------------------------------------------------------------------- +# Install the bluecherry app and dependencies +FROM baseos as bluecherry_install +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /root + +COPY --from=bluecherry_build_cleaned \ + /root/bluecherry-apps/releases/bluecherry_*.deb \ + /root/bluecherry-apps/releases/ + +RUN ls -l /root/bluecherry-apps/releases/ + +# This step is needed if/when building a new bluecherry docker container +# that will connect to an existing bluecherry database. In this case, the +# bluecherry installer will see the existing database, and it needs the +# /etc/bluecherry.conf file to tell it that it is okay to connect to (and +# modify) that database +# +#COPY bluecherry.conf /etc/bluecherry.conf + +ARG MYSQL_ADMIN_LOGIN=root +ARG MYSQL_ADMIN_PASSWORD=root + +# Specific database credentials used by bluecherry server +ARG BLUECHERRY_DB_USER=bluecherry +ARG BLUECHERRY_DB_HOST=172.17.0.1 +ARG BLUECHERRY_DB_PASSWORD=bluecherry +ARG BLUECHERRY_DB_NAME=bluecherry +ARG BLUECHERRY_DB_ACCESS_HOST=% + +# User and Group info used for running bluecherry server processes +ARG BLUECHERRY_LINUX_GROUP_NAME=bluecherry +ARG BLUECHERRY_LINUX_GROUP_ID=1000 +ARG BLUECHERRY_LINUX_USER_NAME=bluecherry +ARG BLUECHERRY_LINUX_USER_ID=1000 + +RUN apt-get update \ + && apt-get install -y \ + rsyslog nmap curl sysstat iproute2 \ + openssl ca-certificates ssl-cert gnupg gnupg2 gnupg1 sudo mysql-client python3-pip wget curl nano cron + +RUN { \ + echo "[client]"; \ + echo "user=$MYSQL_ADMIN_LOGIN"; \ + echo "password=$MYSQL_ADMIN_PASSWORD"; \ + echo "[mysql]"; \ + echo "user=$MYSQL_ADMIN_LOGIN"; \ + echo "password=$MYSQL_ADMIN_PASSWORD"; \ + echo "[mysqldump]"; \ + echo "user=$MYSQL_ADMIN_LOGIN"; \ + echo "password=$MYSQL_ADMIN_PASSWORD"; \ + echo "[mysqldiff]"; \ + echo "user=$MYSQL_ADMIN_LOGIN"; \ + echo "password=$MYSQL_ADMIN_PASSWORD"; \ + } > /root/.my.cnf + +# NOTE: The line "export host=$BLUECHERRY_DB_HOST" ... This is required +# due to a weird global check of this env var by the "check_mysql_admin" +# function in /usr/share/bluecherry/bc_db_tool.sh ... which doesn't accept +# the db host as an argument like most of the other functions in that file. +# --- The Specific problem line is: +# if ! echo "show databases" | mysql_wrapper -h"${host}" -u"$MYSQL_ADMIN_LOGIN" &>/dev/null +# +RUN { \ + echo bluecherry bluecherry/mysql_admin_login string $MYSQL_ADMIN_LOGIN; \ + echo bluecherry bluecherry/mysql_admin_password password $MYSQL_ADMIN_PASSWORD; \ + echo bluecherry bluecherry/db_host string $BLUECHERRY_DB_HOST; \ + echo bluecherry bluecherry/db_userhost string $BLUECHERRY_DB_ACCESS_HOST; \ + echo bluecherry bluecherry/db_name string $BLUECHERRY_DB_NAME; \ + echo bluecherry bluecherry/db_user string $BLUECHERRY_DB_USER; \ + echo bluecherry bluecherry/db_password password $BLUECHERRY_DB_PASSWORD; \ + } | debconf-set-selections \ + && export host=$BLUECHERRY_DB_HOST \ + && apt install -y --no-install-recommends ./bluecherry-apps/releases/bluecherry_*.deb + +# Cleanup tasks +RUN apt-get clean \ +# && rm -f bluecherry-apps/releases/bluecherry_*.deb \ + && rm -rf /var/lib/apt/lists/* + +# Remove these files -- we needed them to build the docker image, since the +# bluecherry installer scripts interact with the database. However, once the +# image is created, we expect it to receive all of the settings/credentials +# from environment variables passed in by docker or docker-compose. +RUN rm -f /root/.my.cnf \ + && rm -f /etc/bluecherry.conf + +# When running rsyslog in a container, we need to disable imklog +# since the in-container process won't be allowed access to it. +RUN sed -i '/imklog/s/^/#/' /etc/rsyslog.conf + +RUN /usr/sbin/groupadd -rf \ + --gid=$BLUECHERRY_LINUX_GROUP_ID \ + $BLUECHERRY_LINUX_GROUP_NAME \ + && /usr/sbin/useradd -rm \ + --comment "Bluecherry DVR" \ + --home-dir=/var/lib/bluecherry \ + --gid=$BLUECHERRY_LINUX_GROUP_NAME \ + --groups=audio,video \ + --uid=$BLUECHERRY_LINUX_USER_ID \ + $BLUECHERRY_LINUX_USER_NAME \ + || echo "bluecherry user already exists" + +RUN mkdir /recordings \ + && chown bluecherry:bluecherry /recordings + +EXPOSE 7001/tcp 7002/tcp + +# This is the main script that runs as process ID 1 in the docker container +COPY entrypoint.sh /entrypoint.sh + +# These scripts are wrappers used to manage the bluecherry database. They are +# necessary because the bluecherry installer usually sets up the database, but +# with a pre-built docker image the installer isn't run (so these actions have +# to be done manually as needed from the docker container... example usage +# from the docker host looks like: +# +# --- CREATE: sudo docker-compose run bluecherry bc-database-create +# --- UPGRADE: sudo docker-compose run bluecherry bc-database-upgrade +COPY bc-database-create.sh /bin/bc-database-create +COPY bc-database-upgrade.sh /bin/bc-database-upgrade + +# This copies in a modified rsyslog config, which tells rsyslog to route +# bluecherry logs to both /var/log/bluecherry.log (within the container) and +# also to the STDOUT of container process with PID 1, which then allows the +# logs to be received by the docker engine (and read via `docker logs` , etc.) +COPY bc-rsyslog.conf /etc/rsyslog.d/10-bluecherry.conf + +# Make the previously copied scripts executable +RUN chmod +x /entrypoint.sh \ + && chmod +x /bin/bc-database-create \ + && chmod +x /bin/bc-database-upgrade + +# Delete the default nginx config, we don't need it. +RUN rm /etc/nginx/sites-enabled/default + +RUN chown bluecherry.bluecherry -R /var/lib/bluecherry +#CMD rm -f /var/run/rsyslogd.pid +#CMD ["/usr/sbin/rsyslogd", "-n", "-f", "/etc/rsyslog.conf"] +#CMD service rsyslog start +CMD /usr/sbin/php-fpm7.4 -D +CMD ["/usr/sbin/nginx", "-g", "daemon off;"] +CMD "/entrypoint.sh" diff --git a/actions/bc-database-create.sh b/actions/bc-database-create.sh new file mode 100644 index 00000000..6a5efcdc --- /dev/null +++ b/actions/bc-database-create.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +# Create a new bluecherry server database, following the same procedure used +# by bluecherry's "official" script from bluecherry-apps:misc/postinstall.sh +# but using environment variables as injected from docker or docker-compose. + +/usr/share/bluecherry/bc_db_tool.sh new_db \ + "$MYSQL_ADMIN_LOGIN" "$MYSQL_ADMIN_PASSWORD" \ + "$BLUECHERRY_DB_NAME" "$BLUECHERRY_DB_USER" "$BLUECHERRY_DB_PASSWORD" \ + "$BLUECHERRY_DB_HOST" "$BLUECHERRY_USERHOST" \ + || exit 1 + +exit 0 diff --git a/actions/bc-database-upgrade.sh b/actions/bc-database-upgrade.sh new file mode 100644 index 00000000..fb5b6188 --- /dev/null +++ b/actions/bc-database-upgrade.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +if ! echo 'exit' | mysql -h "$BLUECHERRY_DB_HOST" -D"$BLUECHERRY_DB_NAME" \ + -u"$BLUECHERRY_DB_USER" -p"$BLUECHERRY_DB_PASSWORD" +then + echo "Can't connect mysql with credentials in /etc/bluecherry.conf" + echo "Please remove config or fix config" + exit 1 +fi + +DB_BACKUP_GZ_FILE=$(mktemp ~bluecherry/bc_db_backup.XXXXXXXXXX.sql.gz) +echo "Going to updgrade Bluecherry DB. Taking a backup into $DB_BACKUP_GZ_FILE just in case" >&2 + +# Backup the DB +mysqldump -h "$BLUECHERRY_DB_HOST" "$BLUECHERRY_DB_NAME" -u"$BLUECHERRY_DB_USER" \ + -p"$BLUECHERRY_DB_PASSWORD" | gzip -c > $DB_BACKUP_GZ_FILE + +if ! /usr/share/bluecherry/bc_db_tool.sh upgrade_db \ + "$BLUECHERRY_DB_NAME" "$BLUECHERRY_DB_USER" \ + "$BLUECHERRY_DB_PASSWORD" "$BLUECHERRY_DB_HOST" +then + echo Failed upgrade database, restoring backup + restore_mysql_backup "$BLUECHERRY_DB_NAME" "$BLUECHERRY_DB_USER" \ + "$BLUECHERRY_DB_PASSWORD" "$BLUECHERRY_DB_HOST" + exit 1 +fi + +# database successfully upgraded +if [[ -f "$DB_BACKUP_GZ_FILE" ]] +then + rm -f "$DB_BACKUP_GZ_FILE" +fi + +exit 0 diff --git a/actions/bc-rsyslog.conf b/actions/bc-rsyslog.conf new file mode 100644 index 00000000..87f9e0f3 --- /dev/null +++ b/actions/bc-rsyslog.conf @@ -0,0 +1,9 @@ +# Save all messages from the bluecherry apps to this log file + +$FileGroup bluecherry + +:programname,isequal,"bc-server" -/var/log/bluecherry.log +:programname,isequal,"bc-server" -/proc/self/fd/1 +:programname,isequal,"bc-server" ~ + +$FileGroup adm diff --git a/actions/depends/README.md b/actions/depends/README.md new file mode 100644 index 00000000..af04525b --- /dev/null +++ b/actions/depends/README.md @@ -0,0 +1 @@ +## This is a binary file built for Ubuntu 20.04. diff --git a/actions/depends/onvif_tool b/actions/depends/onvif_tool new file mode 100644 index 00000000..f458105e Binary files /dev/null and b/actions/depends/onvif_tool differ diff --git a/actions/entrypoint.sh b/actions/entrypoint.sh new file mode 100644 index 00000000..049bb0e8 --- /dev/null +++ b/actions/entrypoint.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +set -e + +echo "> Update MySQL's my.cnf from environment variables passed in from docker" +echo "> Writing /root/.my.cnf" +{ + echo "[client]"; \ + echo "user=$MYSQL_ADMIN_LOGIN"; \ + echo "password=$MYSQL_ADMIN_PASSWORD"; \ + echo "[mysql]"; \ + echo "user=$MYSQL_ADMIN_LOGIN"; \ + echo "password=$MYSQL_ADMIN_PASSWORD"; \ + echo "[mysqldump]"; \ + echo "user=$MYSQL_ADMIN_LOGIN"; \ + echo "password=$MYSQL_ADMIN_PASSWORD"; \ + echo "[mysqldiff]"; \ + echo "user=$MYSQL_ADMIN_LOGIN"; \ + echo "password=$MYSQL_ADMIN_PASSWORD"; \ +} > /root/.my.cnf + +echo "> Update bluecherry server's bluecherry.conf from environment variables passed in from docker" +echo "> Writing /etc/bluecherry.conf" +{ + echo "# Bluecherry configuration file"; \ + echo "# Used to be sure we don't use configurations not suitable for us";\ + echo "version = \"1.0\";"; \ + echo "bluecherry:"; \ + echo "{"; \ + echo " db:"; \ + echo " {"; \ + echo " # 0 = sqlite, 1 = pgsql, 2 = mysql"; \ + echo " type = 2;"; \ + echo " dbname = \"$BLUECHERRY_DB_NAME\";"; \ + echo " user = \"$BLUECHERRY_DB_USER\";"; \ + echo " password = \"$BLUECHERRY_DB_PASSWORD\";"; \ + echo " host = \"$BLUECHERRY_DB_HOST\";"; \ + echo " userhost = \"$BLUECHERRY_DB_ACCESS_HOST\";"; \ + echo " };"; \ + echo "};"; \ +} > /etc/bluecherry.conf + +echo "> chown bluecherry:bluecherry /var/lib/bluecherry/recordings" +chown bluecherry:bluecherry /var/lib/bluecherry/recordings +chown -R bluecherry:bluecherry /var/lib/bluecherry/.local/share/data/ + + +# The bluecherry container's Dockerfile sets rsyslog to route the bluecherry +# server's main log file to STDOUT for process #1, which then gets picked up +# by docker (so its messages get routed out through docker logs, etc.), but +# the location permissions have to be reset on every start of the container: +chmod 777 /proc/self/fd/1 + +# Hack to fix race condition where rsyslog starts too soon and throws errors +# https://github.com/bluecherrydvr/bluecherry-docker/issues/26 + +# sleep for 5 for good measure +sleep 5 + +echo "> /usr/sbin/rsyslogd" +# rm rsyslog.pid to prevent respawning +rm -f /run/rsyslogd.pid +/usr/sbin/rsyslogd +status=$? +if [ $status -ne 0 ]; then + echo "Failed to start rsyslog: $status" + exit $status +fi + +exec "$@" + +/etc/init.d/php7.4-fpm start + + +echo "> /usr/sbin/nginx" +#source /etc/apache2/envvars +/usr/sbin/nginx +status=$? +if [ $status -ne 0 ]; then + echo "Failed to start nginx web server: $status" + exit $status +fi + + +echo "> /usr/sbin/bc-server -u bluecherry -g bluecherry" +export LD_LIBRARY_PATH=/usr/lib/bluecherry +/usr/sbin/bc-server -u bluecherry -g bluecherry +status=$? +if [ $status -ne 0 ]; then + echo "Failed to start bluecherry bc-server: $status" + exit $status +fi + + +# Naive check runs checks once a minute to see if either of the processes exited. +# This illustrates part of the heavy lifting you need to do if you want to run +# more than one service in a container. The container exits with an error +# if it detects that any of the processes has exited. +# Otherwise it loops forever, waking up every 15 seconds +while sleep 15; do + ps aux |grep rsyslog |grep -q -v grep + PROCESS_1_STATUS=$? + ps aux |grep nginx |grep -q -v grep + PROCESS_2_STATUS=$? + ps aux |grep bc-server |grep -q -v grep + PROCESS_3_STATUS=$? + + # If the greps above find anything, they exit with 0 status + # If they are not both 0, then something is wrong + if [ $PROCESS_1_STATUS -ne 0 -o $PROCESS_2_STATUS -ne 0 -o $PROCESS_3_STATUS -ne 0 ]; then + echo "One of the processes has already exited." + exit 1 + fi +done diff --git a/actions/supervisord.conf b/actions/supervisord.conf new file mode 100644 index 00000000..704d06e8 --- /dev/null +++ b/actions/supervisord.conf @@ -0,0 +1,19 @@ +[supervisord] +nodaemon=true + +[program:apache2] +command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND" + +[program:bluecherry] +environment=LD_LIBRARY_PATH=/usr/lib/bluecherry +command=/usr/sbin/bc-server -s -u bluecherry -g bluecherry +autostart=true +autorestart=true +startretries=3 + +[program:rsyslog] +command=/usr/sbin/rsyslogd -n +autostart=true +autorestart=true +startretries=3 +redirect_stderr=true