diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e2c778f --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,26 @@ +name: Docker Image CI + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Docker Login + env: + DOCKER_USER: ${{secrets.DOCKER_USER}} + DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}} + run: docker login -u $DOCKER_USER -p $DOCKER_PASSWORD + - name: Docker Build + run: docker build . --file Dockerfile --tag ${{secrets.DOCKER_USER}}/elodie:$(date +%s) --tag ${{secrets.DOCKER_USER}}/elodie:latest + - name: Docker Publish + run: docker push --all-tags ${{secrets.DOCKER_USER}}/elodie \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index df8f050..59b8c55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,50 +1,54 @@ -FROM python:3-alpine AS builder +FROM python:3.9-alpine AS builder ENV PYTHONUNBUFFERED 1 # Install runtime dependencies RUN apk --update upgrade && \ - apk add --update inotify-tools exiftool zlib jpeg lcms2 tiff openjpeg su-exec && \ - rm -rf /var/cache/apk/* - + apk add --update inotify-tools exiftool zlib jpeg lcms2 tiff openjpeg su-exec && \ + rm -rf /var/cache/apk/* + # Install build dependencies RUN apk --update upgrade && \ - apk add --update git \ - build-base \ - jpeg-dev \ - zlib-dev \ - lcms2-dev \ - openjpeg-dev \ - tiff-dev && \ - rm -rf /var/cache/apk/* + apk add --update git \ + build-base \ + jpeg-dev \ + zlib-dev \ + lcms2-dev \ + openjpeg-dev \ + tiff-dev && \ + rm -rf /var/cache/apk/* WORKDIR /wheels -RUN git clone https://github.com/jmathai/elodie.git /elodie && \ - pip wheel --no-cache-dir -r /elodie/requirements.txt && \ - rm -rf /elodie/.git +RUN git clone https://github.com/D3Zyre/Elodie.git /elodie && \ + pip wheel --no-cache-dir -r /elodie/requirements.txt && \ + rm -rf /elodie/.git + +#Using internal modified copy of file for now +#RUN git clone https://github.com/javanile/inotifywait-polling.git /inotifywait-polling && \ +# chmod +x /inotifywait-polling/inotifywait-polling.sh -FROM python:3-alpine +FROM python:3.9-alpine LABEL maintainer="pierre@buyle.org" ENV PYTHONUNBUFFERED 1 # Install runtime dependencies RUN apk --update upgrade && \ - apk add --update inotify-tools exiftool zlib jpeg lcms2 tiff openjpeg su-exec && \ - rm -rf /var/cache/apk/* + apk add --update inotify-tools exiftool zlib jpeg lcms2 tiff openjpeg su-exec bash findutils && \ + rm -rf /var/cache/apk/* COPY --from=builder /wheels /wheels COPY --from=builder /elodie /elodie +#COPY --from=builder /inotifywait-polling/inotifywait-polling.sh /usr/local/bin/inotifywait-polling WORKDIR /elodie RUN pip install --no-cache-dir -r requirements.txt -f /wheels && \ - rm -rf /wheels - + rm -rf /wheels + COPY entrypoint.sh /entrypoint.sh +COPY --chown=root:root --chmod=0777 inotifywait-polling.sh /usr/local/bin/inotifywait-polling -ENV PUID=1000 -ENV PGID=1000 ENV SOURCE=/source ENV DESTINATION=/destination -ENV ELODIE_APPLICATION_DIRECTORY=/elodie -ENV DEFAULT_COMMAND=watch +ENV ELODIE_APPLICATION_DIRECTORY=~/.elodie +ENV ELODIE_DEFAULT_COMMAND=watch ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/README.md b/README.md index 6d7a216..71c9a2f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # elodie-docker -Docker container for [Elodie](https://github.com/jmathai/elodie) +Docker container for a more recently updated fork of [Elodie](https://github.com/D3Zyre/Elodie.git) -

+

- -The container default command is to watch a source folder using `inotifywait` and automatically execute `elodie` to import added images to a destination folder. +The container default command is to watch a source folder using `inotifywait` or [`inotifywait-polling`](https://github.com/javanile/inotifywait-polling) and automatically execute `elodie` to import added images to a destination folder. ## Usage @@ -32,10 +31,11 @@ docker run \ | DESTINATION | /destination | Copy imported files into this directory | TRASH | | Set to move files to this trash folder after copying files | EXCLUDE | | Regular expression for directories or files to exclude -| ALLOW_DUPLICATE | | Set to import the files even if theu have already been imported +| ALLOW_DUPLICATE | | Set to import the files even if theu have already been imported | ALBUM_FROM_FOLDER | | Set to use images' folders as their album names | MAPQUEST_KEY | | Mapquest API key for geo-code location from EXIF's GPS fields | DEBUG | | Set to enable debug messages +| INOTIFYWAIT_POLLING | | Set to enable use of inotifywait polling. Useful for CIFS/SMB/NFS mounts. ### Advanced configurtion @@ -53,7 +53,7 @@ docker run \ pbuyle/elodie ``` -Note: If a custom configuartion file is used then setting the `MAPQUEST_KEY` environment variable will have not effect. +Note: If a custom configuration file is used then setting the `MAPQUEST_KEY` environment variable will have not effect. ### User / Group Identifiers @@ -62,7 +62,8 @@ When using volumes (-v flags) permissions issues can arise between the host OS a Ensure any volume directories on the host are owned by the same user you specify and any permissions issues will vanish like magic. In this instance PUID=1000 and PGID=1000, to find yours use id user as below: + ``` $ id username uid=1000(dockeruser) gid=1000(dockergroup) groups=1000(dockergroup) -``` \ No newline at end of file +``` diff --git a/entrypoint.sh b/entrypoint.sh index b6e3738..1d1a65d 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -4,6 +4,7 @@ set -eu PYTHON="su-exec ${PUID}:${PGID} python" ELODIE="${PYTHON} /elodie/elodie.py" +INOTIFY_BIN="inotifywait" IMPORT_OPTIONS="--destination ${DESTINATION}" @@ -29,13 +30,17 @@ if [ "${DEBUG:-}" ]; then IMPORT_OPTIONS="${IMPORT_OPTIONS} --debug" fi +if [ "${INOTIFYWAIT_POLLING:-}" ]; then + INOTIFY_BIN="inotifywait-polling" +fi + if [ ! -e "${ELODIE_APPLICATION_DIRECTORY}/config.ini" ] && [ "${MAPQUEST_KEY:-}" ] ; then echo -e "[MapQuest]\nkey=${MAPQUEST_KEY}\nprefer_english_names=False" > "${ELODIE_APPLICATION_DIRECTORY}/config.ini" fi case "${1:-watch}" in watch) - inotifywait -e close_write -mr "${SOURCE}" | while read -r EV; + ${INOTIFY_BIN} -e close_write -mr "${SOURCE}" | while read -r EV; do ( NOW=$($PYTHON -c "import time;print(time.time());") diff --git a/inotifywait-polling.sh b/inotifywait-polling.sh new file mode 100644 index 0000000..374b682 --- /dev/null +++ b/inotifywait-polling.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash + +## +# inotifywait-polling +# +# REQUIRES: inotifywait in full BASH. +# +# Copyright (c) 2020 Francesco Bianco +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +## + +[[ -z "${LCOV_DEBUG}" ]] || set -x + +set -e +#set -xv + +export LC_ALL=C + +#trap '[[ -z "$(jobs -p)" ]] || kill $(jobs -p)' EXIT +trap 'kill $(jobs -p) > /dev/null 2>&1' EXIT + +usage () { + cat <<'EOF' +inotifywait 3.14 +Wait for a particular event on a file or set of files. +Usage: inotifywait [ options ] file1 [ file2 ] [ file3 ] [ ... ] +Options: + -h|--help Show this help text. + @ Exclude the specified file from being watched. + --exclude + Exclude all events on files matching the + extended regular expression . + --excludei + Like --exclude but case insensitive. + -m|--monitor Keep listening for events forever. Without + this option, inotifywait will exit after one + event is received. + -d|--daemon Same as --monitor, except run in the background + logging events to a file specified by --outfile. + Implies --syslog. + -r|--recursive Watch directories recursively. + --fromfile + Read files to watch from or `-' for stdin. + -o|--outfile + Print events to rather than stdout. + -s|--syslog Send errors to syslog rather than stderr. + -q|--quiet Print less (only print events). + -qq Print nothing (not even events). + --format Print using a specified printf-like format + string; read the man page for more details. + --timefmt strftime-compatible format string for use with + %T in --format string. + -c|--csv Print events in CSV format. + -t|--timeout + When listening for a single event, time out after + waiting for an event for seconds. + If is 0, inotifywait will never time out. + -e|--event [ -e|--event ... ] + Listen for specific event(s). If omitted, all events are + listened for. + +Exit status: + 0 - An event you asked to watch for was received. + 1 - An event you did not ask to watch for was received + (usually delete_self or unmount), or some error occurred. + 2 - The --timeout option was given and no events occurred + in the specified interval of time. + +Events: + access file or directory contents were read + modify file or directory contents were written + attrib file or directory attributes changed + close_write file or directory closed, after being opened in + writable mode + close_nowrite file or directory closed, after being opened in + read-only mode + close file or directory closed, regardless of read/write mode + open file or directory opened + moved_to file or directory moved to watched directory + moved_from file or directory moved from watched directory + move file or directory moved to or from watched directory + create file or directory created within watched directory + delete file or directory deleted within watched directory + delete_self file or directory was deleted + unmount file system containing file or directory unmounted +EOF +} + +quiet= +recursive= +options=$(getopt -n inotifywait -o qrhme: -l help -- "$@" && true) +eval set -- "${options}" +#echo "options: ${options}" + +while true; do + case "$1" in + -q) quiet=1 ;; + -r) recursive=1 ;; + -e) shift; events=${1^^} ;; + -h|--help) usage; exit ;; + --) shift; break ;; + esac + shift +done + +## +# +## +watch () { + #echo "watch $1" + find $1 -printf "%s %y %p %T@\\n" | sort -k3 - > $1.inotifywait + while true; do + sleep 2 + sign= + last=$(cat $1.inotifywait) + #mv $1.inotifywait $1.$(date +%s).inotifywait + find $1 -printf "%s %y %p %T@\\n" | sort -k3 - > $1.inotifywait + meta=$(diff <(echo "${last}") <(cat "$1.inotifywait")) && true + [[ -z "${meta}" ]] && continue + echo -e "${meta}\n." | while IFS= read line || [[ -n "${line}" ]]; do + echo "line: ${line}" + if [[ "${line}" == "." ]]; then + echo "sign: ${sign}" + for item in $(tr ';' '\n' <<< "${sign}"); do + echo "item: ${item}" + event=$(echo ${item} | cut -s -d':' -f1) + focus=$(echo ${item} | cut -s -d':' -f2) + dir=$(dirname "${focus}")/ + echo "focus: ${focus}" + file=$(basename "${focus}") + print_event ${dir} ${event} ${file} + done + break + fi + flag=$(echo ${line} | cut -s -d' ' -f1) + file=$(echo ${line} | cut -s -d' ' -f3-) + [[ -n "${file}" ]] || continue + #echo "${file} -- ${file: -12}" + [[ "${file: -12}" != ".inotifywait" ]] || continue + case ${flag} in + "<") + event=DELETE + ;; + ">") + event=CREATE + if [[ "${sign}" == *"DELETE:${file};"* ]]; then + event=MODIFY + sign=$(echo "${sign}" | sed "s#DELETE:${file};##g") + elif [[ "${sign}" == *"DELETE:"* ]]; then + event=MOVED_TO + sign=$(echo "${sign}" | sed "s#DELETE:.*;##g") + fi + ;; + esac + sign+="${event}:${file};" + done + done + return 0 +} + +## +# +## +print_event () { + [[ -z "${events}" || "${events}" == *"$2"* ]] && echo "$1 $2 $3" + case "$2" in + CREATE) + [[ -z "${events}" || "${events}" == *"OPEN"* ]] && echo "$1 OPEN $3" + [[ -z "${events}" || "${events}" == *"MODIFY"* ]] && echo "$1 MODIFY $3" + [[ -z "${events}" || "${events}" == *"CLOSE"* ]] && echo "$1 CLOSE_WRITE,CLOSE $3" + ;; + esac + return 0 +} + +## +# Entrypoint +## +main () { + if [[ -z "$1" ]]; then + >&2 echo "No files specified to watch!" + exit 1 + fi + + [[ -z "${quiet}" ]] && >&2 echo "Setting up watches." + + for file in "$@"; do + if [[ ! -e "${file}" ]]; then + echo "Couldn't watch $1: No such file or directory" + exit 1 + fi + watch ${file} & + done + + [[ -z "${quiet}" ]] && >&2 echo "Watches established." + sleep infinity + exit 0 +} + +## +main "$@" \ No newline at end of file