diff --git a/Dockerfile b/Dockerfile index 3c057d67423..5dfce593f0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,41 +39,55 @@ RUN pip install --target=/dependencies playwright~=1.41.2 \ FROM python:${PYTHON_VERSION}-slim-bookworm LABEL org.opencontainers.image.source="https://github.com/dgtlmoon/changedetection.io" -RUN apt-get update && apt-get install -y --no-install-recommends \ - libxslt1.1 \ - # For presenting price amounts correctly in the restock/price detection overview - locales \ - # For pdftohtml - poppler-utils \ - zlib1g \ - && apt-get clean && rm -rf /var/lib/apt/lists/* - - -# https://stackoverflow.com/questions/58701233/docker-logs-erroneously-appears-empty-until-container-stops -ENV PYTHONUNBUFFERED=1 - -RUN [ ! -d "/datastore" ] && mkdir /datastore +RUN set -ex; \ + apt-get update && apt-get install -y --no-install-recommends \ + gosu \ + libxslt1.1 \ + # For presenting price amounts correctly in the restock/price detection overview + locales \ + # For pdftohtml + poppler-utils \ + zlib1g && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/*; \ + useradd -u 911 -U -m -s /bin/false changedetection && \ + usermod -G users changedetection; \ + mkdir -p /datastore # Re #80, sets SECLEVEL=1 in openssl.conf to allow monitoring sites with weak/old cipher suites RUN sed -i 's/^CipherString = .*/CipherString = DEFAULT@SECLEVEL=1/' /etc/ssl/openssl.cnf # Copy modules over to the final image and add their dir to PYTHONPATH COPY --from=builder /dependencies /usr/local -ENV PYTHONPATH=/usr/local +ENV PYTHONPATH=/usr/local \ + # https://stackoverflow.com/questions/58701233/docker-logs-erroneously-appears-empty-until-container-stops + PYTHONUNBUFFERED=1 \ + # https://stackoverflow.com/questions/64808915/should-pycache-folders-be-included-in-production-containers + # This avoids permission denied errors because the app directory is root-owned. + PYTHONDONTWRITEBYTECODE=1 \ + DATASTORE_PATH="/datastore" \ + # Disable creation of Pytest cache dir when running tests inside the container by default + PYTEST_ADDOPTS="-p no:cacheprovider" EXPOSE 5000 +# The entrypoint script handling PUID/PGID and permissions +COPY --chmod=755 docker-entrypoint.sh /app/docker-entrypoint.sh + # The actual flask app module COPY changedetectionio /app/changedetectionio # Starting wrapper COPY changedetection.py /app/changedetection.py +# create test directory for pytest to run in +RUN mkdir -p /app/changedetectionio/test-datastore && \ + chown changedetection:changedetection /app/changedetectionio/test-datastore + # Github Action test purpose(test-only.yml). # On production, it is effectively LOGGER_LEVEL=''. ARG LOGGER_LEVEL='' ENV LOGGER_LEVEL "$LOGGER_LEVEL" WORKDIR /app -CMD ["python", "./changedetection.py", "-d", "/datastore"] - - +ENTRYPOINT ["/app/docker-entrypoint.sh"] +CMD ["python", "/app/changedetection.py"] diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index 70afc105926..021cdf22798 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -42,15 +42,17 @@ def main(): global datastore global app - datastore_path = None + datastore_path = os.environ.get('DATASTORE_PATH') do_cleanup = False host = '' ipv6_enabled = False port = os.environ.get('PORT') or 5000 ssl_mode = False + if datastore_path is not None: + pass # On Windows, create and use a default path. - if os.name == 'nt': + elif os.name == 'nt': datastore_path = os.path.expandvars(r'%APPDATA%\changedetection.io') os.makedirs(datastore_path, exist_ok=True) else: diff --git a/changedetectionio/tests/conftest.py b/changedetectionio/tests/conftest.py index 50f7104b145..308af90cecd 100644 --- a/changedetectionio/tests/conftest.py +++ b/changedetectionio/tests/conftest.py @@ -49,9 +49,6 @@ def measure_memory_usage(request): s = f"Peak memory used by the test {request.node.fspath} - '{request.node.name}': {max_memory_used:.2f} MB" logger.debug(s) - with open("test-memory.log", 'a') as f: - f.write(f"{s}\n") - # Assert that the memory usage is less than 200MB # assert max_memory_used < 150, f"Memory usage exceeded 200MB: {max_memory_used:.2f} MB" @@ -109,6 +106,6 @@ def teardown(): app.config.exit.set() cleanup(app_config['datastore_path']) - + request.addfinalizer(teardown) yield app diff --git a/changedetectionio/tests/test_backup.py b/changedetectionio/tests/test_backup.py index 1dffc005356..ceb92ecdf8b 100644 --- a/changedetectionio/tests/test_backup.py +++ b/changedetectionio/tests/test_backup.py @@ -67,4 +67,4 @@ def test_backup(client, live_server, measure_memory_usage): follow_redirects=True ) - assert b'No backups found.' in res.data \ No newline at end of file + assert b'No backups found.' in res.data diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 00000000000..f0ce942d78c --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -eu + +DATASTORE_PATH="${DATASTORE_PATH:-/datastore}" + +# If the first argument looks like a flag, assume we want to run changedetection +if [ "${1:0:1}" = '-' ]; then + set -- python /app/changedetection.py "$@" +fi + +# If we're running as root, by default make sure process uid/gid +# and datadir permissions are correct. This can be skipped by setting +# KEEP_PRIVILEGES to something non-empty. +if [ "$(id -u)" = '0' -a -z "${KEEP_PRIVILEGES:-}" ]; then + PUID=${PUID:-911} + PGID=${PGID:-911} + + groupmod -o -g "$PGID" changedetection + usermod -o -u "$PUID" changedetection + + # Check if the supplied uid/gid grants write permissions on the datastore + # root directory. Only if it does not, chown it recursively. + # In my testing, `test -w "$DATASTORE_PATH"` did not work reliably. + tempfile="$DATASTORE_PATH/.check-writable" + gosu changedetection:changedetection bash -c ">> '$tempfile'" && + rm -f "$tempfile" || + chown -R changedetection:changedetection "$DATASTORE_PATH" || + ( + echo "Failed to change permissions on $DATASTORE_PATH. Ensure it is writable by $PUID:$PGID" >&2 + exit 1 + ) + + # Ensure the home directory's permissions are adjusted as well. + chown -R changedetection:changedetection ~changedetection + + # Restart this script as an unprivileged user + exec gosu changedetection:changedetection "$BASH_SOURCE" "$@" +fi + +exec "$@"