From df85faff8b79c21177aaf0a5c1f8cd45e0c33919 Mon Sep 17 00:00:00 2001 From: asonix Date: Tue, 16 Jun 2020 15:55:24 -0500 Subject: [PATCH] Start work on webp support --- client-examples/1.webp | Bin 0 -> 30320 bytes docker/dev/Dockerfile | 264 +++++++++++++++++++-------------- docker/dev/docker-compose.yml | 2 +- docker/prod/Dockerfile.amd64 | 244 +++++++++++++++++------------- docker/prod/Dockerfile.arm32v7 | 248 ++++++++++++++++++------------- docker/prod/Dockerfile.arm64v8 | 247 +++++++++++++++++------------- src/config.rs | 4 +- src/error.rs | 3 + src/main.rs | 5 +- src/validate.rs | 115 ++++++++------ 10 files changed, 667 insertions(+), 465 deletions(-) create mode 100644 client-examples/1.webp diff --git a/client-examples/1.webp b/client-examples/1.webp new file mode 100644 index 0000000000000000000000000000000000000000..122741b605f3121d393829ffb5b7a0924db13c86 GIT binary patch literal 30320 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 TU=pkm8Veub3_|1vJMaJi)t)C5 literal 0 HcmV?d00001 diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 0b9eb49..fce76d5 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -1,8 +1,37 @@ -FROM rustembedded/cross:x86_64-unknown-linux-gnu AS x86_64-builder +# Target environment +FROM amd64/ubuntu:20.04 as target-env + +ENV \ + TARGET=x86_64-unknown-linux-gnu \ + BUILD_MODE=release + +# Basic cross-build environment +FROM ubuntu:20.04 as cross-build + +ENV \ + ARCH=amd64 \ + HOST=x86_64-unknown-linux \ + TOOL=x86_64-linux-gnu \ + TARGET=x86_64-unknown-linux-gnu \ + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc \ + CC_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-gcc \ + CXX_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-g++ \ + BUILD_MODE=release + +ENV \ + TOOLCHAIN=stable \ + DEBIAN_FRONTEND=noninteractive \ + PKG_CONFIG_ALLOW_CROSS=1 \ + PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/pkgconfig \ + LD_LIBRARY_PATH=/usr/lib/$TOOL:/usr/$TOOL/lib \ + LD_RUN_PATH=/usr/lib/$TOOL:/usr/$TOOL/lib \ + LDFLAGS="-L/usr/lib/$TOOL -L/usr/$TOOL/lib -Wl,-rpath-link,/usr/lib/$TOOL -Wl,-rpath-link,/usr/$TOOL/lib" \ + CFLAGS="-I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" \ + CPPFLAGS="-I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" RUN \ - apt-get update && \ - apt-get upgrade -y && \ + sed 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch-=amd64,i386] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list > /etc/apt/sources.list.d/ports.list && \ + sed -i 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch=amd64,i386] http:\/\/\1.archive.ubuntu.com\/ubuntu\//g' /etc/apt/sources.list && \ addgroup --gid 991 build && \ adduser \ --disabled-password \ @@ -10,167 +39,174 @@ RUN \ --ingroup build \ --uid 991 \ --home /opt/build \ - build - -ADD https://sh.rustup.rs /opt/build/rustup.sh - -RUN \ - chown -R build:build /opt/build - -USER build -WORKDIR /opt/build - -ENV \ - PATH=$PATH:/opt/build/.cargo/bin \ - TOOLCHAIN=stable \ - HOST=x86_64-unknown-linux \ - TARGET=x86_64-unknown-linux-gnu \ - TOOL=x86_64-linux-gnu \ - ARCH=amd64 - -RUN \ - chmod +x rustup.sh && \ - ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ - rustup target add $TARGET - -FROM x86_64-builder as builder - -USER root - -ADD https://imagemagick.org/download/ImageMagick.tar.gz ./ - -RUN \ + build && \ dpkg --add-architecture $ARCH && \ apt-get update && \ - apt-get -y install \ - libgexiv2-dev:$ARCH \ + apt-get upgrade -y && \ + apt-get install -y \ + pkg-config \ + build-essential \ + crossbuild-essential-$ARCH + +WORKDIR /opt/build + + +# Environment for ImageMagick +FROM cross-build as imagemagick-builder + +RUN \ + apt-get install -y \ libltdl-dev:$ARCH \ libjpeg-dev:$ARCH \ libpng-dev:$ARCH \ libwebp-dev:$ARCH \ liblzma-dev:$ARCH \ - llvm-dev \ - libclang-dev \ - clang && \ - chown build:build ImageMagick.tar.gz + libxml2-dev:$ARCH +ADD --chown=build:build https://imagemagick.org/download/ImageMagick.tar.gz /opt/build/ImageMagick.tar.gz USER build RUN \ - tar xvzf ImageMagick.tar.gz && \ + tar zxf ImageMagick.tar.gz && \ mv ImageMagick-* ImageMagick WORKDIR /opt/build/ImageMagick -ENV \ - USER=build \ - PKG_CONFIG_ALLOW_CROSS=1 \ - PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/pkgconfig \ - LD_LIBRARY_PATH=/usr/lib/$TOOL \ - LD_RUN_PATH=$LD_RUN_PATH:/usr/lib/$TOOL \ - LDFLAGS="$LDFLAGS -L/usr/lib/$TOOL -Wl,-rpath-link,/usr/lib/$TOOL" \ - CFLAGS="$CFLAGS -I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" \ - CPPFLAGS="$CPPFLAGS -I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" - RUN \ ./configure \ - CC=$TOOL-gcc \ - CXX=$TOOL-g++ \ - --prefix=/imagemagick \ - --with-modules \ - --enable-shared \ - --disable-static \ - --without-perl \ - --with-xml=yes \ - --with-png=yes \ - --with-jpeg=yes \ - --with-webp=yes \ - --host=$HOST && \ + CC=$TOOL-gcc \ + CXX=$TOOL-g++ \ + --enable-shared \ + --with-modules \ + --disable-static \ + --disable-docs \ + --prefix=/usr/local \ + --with-utilities=no \ + --without-perl \ + --with-xml=yes \ + --with-png=yes \ + --with-jpeg=yes \ + --with-webp=yes \ + --host=$HOST && \ make USER root RUN \ make install && \ - ldconfig /imagemagick/lib + ldconfig /usr/local/lib +# Environment for Rust +FROM cross-build as rust + +RUN \ + apt-get install -y curl + +ENV \ + PATH=$PATH:/opt/build/.cargo/bin + +ADD --chown=build:build https://sh.rustup.rs /opt/build/rustup.sh + +USER build + +RUN \ + chmod +x rustup.sh && \ + ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ + rustup target add $TARGET + +USER root + + +# Environment for pict-rs +FROM cross-build as pict-rs-builder + +RUN \ + apt-get install -y \ + libgexiv2-dev:$ARCH \ + libxml2:$ARCH \ + libltdl7:$ARCH \ + llvm-dev \ + libclang-dev \ + clang + +ENV \ + PATH=$PATH:/opt/build/.cargo/bin \ + PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig \ + LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib \ + LD_RUN_PATH=$LD_RUN_PATH:/usr/local/lib \ + LDFLAGS="$LDFLAGS -L/usr/local/lib" \ + IMAGE_MAGICK_LIB_DIRS=/usr/local/lib \ + IMAGE_MAGICK_INCLUDE_DIRS=/usr/local/include/ImageMagick-7 \ + CFLAGS="$CFLAGS -I/usr/local/include/ImageMagick-7" \ + CPPFLAGS="$CPPFLAGS -I/usr/local/include/ImageMagick-7" \ + RUSTFLAGS="-L/usr/lib/$TOOL -C link-arg=-Wl,-rpath-link,/usr/lib/$TOOL -L/usr/$TOOL/lib -C link-arg=-Wl,-rpath-link,/usr/$TOOL/lib" + +COPY --from=rust --chown=build:build /opt/build/.cargo /opt/build/.cargo +COPY --from=rust --chown=build:build /opt/build/.rustup /opt/build/.rustup +COPY --from=imagemagick-builder /usr/local/ /usr/local + USER build WORKDIR /opt/build RUN \ - cargo new repo + USER=build cargo new repo WORKDIR /opt/build/repo -COPY Cargo.toml Cargo.lock ./ - -USER root -RUN \ - chown -R build:build ./ - -USER build - -ENV \ - LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/imagemagick/lib \ - LD_RUN_PATH=$LD_RUN_PATH:/imagemagick/lib \ - LDFLAGS="$LDFLAGS -L/imagemagick/lib" \ - RUSTFLAGS="-L/usr/lib/$TOOL -C link-arg=-Wl,-rpath-link,/usr/lib/$TOOL" \ - IMAGE_MAGICK_LIB_DIRS=/imagemagick/lib \ - IMAGE_MAGICK_INCLUDE_DIRS=/imagemagick/include/ImageMagick-7 \ - PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/imagemagick/lib/pkgconfig \ - CFLAGS="$CFLAGS -I/imagemagick/include/ImageMagick-7" \ - CPPFLAGS="$CPPFLAGS -I/imagemagick/include/ImageMagick-7" +COPY --chown=build:build Cargo.toml Cargo.lock ./ RUN \ mkdir -p ./src && \ echo 'fn main() { println!("Dummy") }' > ./src/main.rs && \ - cargo build --release && \ - rm -rf ./src + USER=build cargo build --$BUILD_MODE && \ + rm -rf ./src && \ + rm -rf ./target/$BUILD_MODE/deps/pict_rs-* -COPY src ./src/ - -USER root -RUN \ - chown -R build:build ./src && \ - rm -r ./target/release/deps/pict_rs-* - -USER build - -RUN cargo build --release --frozen - -FROM ubuntu:20.04 - -ARG UID=1000 -ARG GID=1000 -ARG BINARY=pict-rs -ARG TARGET=x86_64-unknown-linux-gnu +COPY --chown=build:build src ./src/ + +RUN \ + cargo build --$BUILD_MODE --frozen + +# Producing target binary +FROM target-env + +ARG UID=991 +ARG GID=991 RUN \ - apt-get update && \ - apt-get -y upgrade && \ - apt-get -y install \ - tini \ - libgexiv2-2 \ - libgomp1 \ - libltdl7 && \ addgroup --gid $GID pictrs && \ adduser \ --disabled-password \ --gecos "" \ --ingroup pictrs \ --uid $UID \ - --home /opt/pictrs \ - pictrs + --home /opt/pict-rs \ + pictrs && \ + apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y \ + libgexiv2-2 \ + libpng16-16 \ + libjpeg8 \ + libwebp6 \ + libwebpdemux2 \ + libwebpmux3 \ + libltdl7 \ + libgomp1 \ + libxml2 \ + tini -COPY --from=builder /imagemagick /imagemagick -COPY --from=builder /opt/build/repo/target/release/$BINARY /usr/bin/$BINARY +COPY --from=pict-rs-builder /opt/build/repo/target/$BUILD_MODE/pict-rs /usr/local/bin/pict-rs +COPY --from=imagemagick-builder /usr/local /usr/local + +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib VOLUME /mnt -WORKDIR /opt/pictrs +WORKDIR /opt/pict-rs USER pictrs EXPOSE 8080 ENTRYPOINT ["/usr/bin/tini", "--"] -CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080"] +CMD ["/usr/local/bin/pict-rs", "-p", "/mnt"] diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index 138529e..48a94b0 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -12,6 +12,6 @@ services: - "127.0.0.1:8080:8080" restart: always environment: - - PICTRS_PATH=/app/data + - RUST_LOG=info,pict_rs=debug volumes: - ./volumes/pictrs:/mnt diff --git a/docker/prod/Dockerfile.amd64 b/docker/prod/Dockerfile.amd64 index 21b77f1..0e9d098 100644 --- a/docker/prod/Dockerfile.amd64 +++ b/docker/prod/Dockerfile.amd64 @@ -1,8 +1,37 @@ -FROM rustembedded/cross:x86_64-unknown-linux-gnu AS x86_64-builder +# Target environment +FROM amd64/ubuntu:20.04 as target-env + +ENV \ + TARGET=x86_64-unknown-linux-gnu \ + BUILD_MODE=release + +# Basic cross-build environment +FROM ubuntu:20.04 as cross-build + +ENV \ + ARCH=amd64 \ + HOST=x86_64-unknown-linux \ + TOOL=x86_64-linux-gnu \ + TARGET=x86_64-unknown-linux-gnu \ + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc \ + CC_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-gcc \ + CXX_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-g++ \ + BUILD_MODE=release + +ENV \ + TOOLCHAIN=stable \ + DEBIAN_FRONTEND=noninteractive \ + PKG_CONFIG_ALLOW_CROSS=1 \ + PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/pkgconfig \ + LD_LIBRARY_PATH=/usr/lib/$TOOL:/usr/$TOOL/lib \ + LD_RUN_PATH=/usr/lib/$TOOL:/usr/$TOOL/lib \ + LDFLAGS="-L/usr/lib/$TOOL -L/usr/$TOOL/lib -Wl,-rpath-link,/usr/lib/$TOOL -Wl,-rpath-link,/usr/$TOOL/lib" \ + CFLAGS="-I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" \ + CPPFLAGS="-I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" RUN \ - apt-get update && \ - apt-get upgrade -y && \ + sed 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch-=amd64,i386] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list > /etc/apt/sources.list.d/ports.list && \ + sed -i 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch=amd64,i386] http:\/\/\1.archive.ubuntu.com\/ubuntu\//g' /etc/apt/sources.list && \ addgroup --gid 991 build && \ adduser \ --disabled-password \ @@ -10,153 +39,168 @@ RUN \ --ingroup build \ --uid 991 \ --home /opt/build \ - build - -ADD https://sh.rustup.rs /opt/build/rustup.sh - -RUN \ - chown -R build:build /opt/build - -USER build -WORKDIR /opt/build - -ENV \ - PATH=$PATH:/opt/build/.cargo/bin \ - TOOLCHAIN=stable \ - HOST=x86_64-unknown-linux \ - TARGET=x86_64-unknown-linux-gnu \ - TOOL=x86_64-linux-gnu \ - ARCH=amd64 - -RUN \ - chmod +x rustup.sh && \ - ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ - rustup target add $TARGET - - -FROM x86_64-builder as builder - -USER root - -ADD https://imagemagick.org/download/ImageMagick.tar.gz ./ - -RUN \ + build && \ dpkg --add-architecture $ARCH && \ apt-get update && \ - apt-get -y install \ - libgexiv2-dev:$ARCH \ + apt-get upgrade -y && \ + apt-get install -y \ + pkg-config \ + build-essential \ + crossbuild-essential-$ARCH + +WORKDIR /opt/build + + +# Environment for ImageMagick +FROM cross-build as imagemagick-builder + +RUN \ + apt-get install -y \ libltdl-dev:$ARCH \ libjpeg-dev:$ARCH \ libpng-dev:$ARCH \ libwebp-dev:$ARCH \ - libxml2-dev:$ARCH \ liblzma-dev:$ARCH \ - llvm-dev \ - libclang-dev \ - clang && \ - chown build:build ImageMagick.tar.gz + libxml2-dev:$ARCH +ADD --chown=build:build https://imagemagick.org/download/ImageMagick.tar.gz /opt/build/ImageMagick.tar.gz USER build RUN \ - tar xzf ImageMagick.tar.gz && \ + tar zxf ImageMagick.tar.gz && \ mv ImageMagick-* ImageMagick WORKDIR /opt/build/ImageMagick -ENV \ - USER=build \ - PKG_CONFIG_ALLOW_CROSS=1 \ - PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/pkgconfig \ - LD_LIBRARY_PATH=/usr/lib/$TOOL \ - LD_RUN_PATH=$LD_RUN_PATH:/usr/lib/$TOOL \ - LDFLAGS="$LDFLAGS -L/usr/lib/$TOOL -Wl,-rpath-link,/usr/lib/$TOOL" \ - CFLAGS="$CFLAGS -I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" \ - CPPFLAGS="$CPPFLAGS -I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" - RUN \ ./configure \ - CC=$TOOL-gcc \ - CXX=$TOOL-g++ \ - --prefix=/imagemagick \ - --disable-docs \ - --with-modules \ - --enable-shared \ - --disable-static \ - --without-perl \ - --with-xml=yes \ - --with-png=yes \ - --with-jpeg=yes \ - --with-webp=yes \ - --host=$HOST && \ + CC=$TOOL-gcc \ + CXX=$TOOL-g++ \ + --enable-shared \ + --with-modules \ + --disable-static \ + --disable-docs \ + --prefix=/usr/local \ + --with-utilities=no \ + --without-perl \ + --with-xml=yes \ + --with-png=yes \ + --with-jpeg=yes \ + --with-webp=yes \ + --host=$HOST && \ make USER root RUN \ make install && \ - ldconfig /imagemagick/lib + ldconfig /usr/local/lib + + +# Environment for Rust +FROM cross-build as rust + +RUN \ + apt-get install -y curl + +ENV \ + PATH=$PATH:/opt/build/.cargo/bin + +ADD --chown=build:build https://sh.rustup.rs /opt/build/rustup.sh USER build -WORKDIR /opt/build +RUN \ + chmod +x rustup.sh && \ + ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ + rustup target add $TARGET -ARG TAG=master -ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs -ARG BINARY=pict-rs +USER root + + +# Environment for pict-rs +FROM cross-build as pict-rs-builder RUN \ - git clone -b $TAG $REPOSITORY repo - -WORKDIR /opt/build/repo + apt-get install -y \ + libgexiv2-dev:$ARCH \ + libxml2:$ARCH \ + libltdl7:$ARCH \ + llvm-dev \ + libclang-dev \ + clang ENV \ - LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/imagemagick/lib \ - LD_RUN_PATH=$LD_RUN_PATH:/imagemagick/lib \ - LDFLAGS="$LDFLAGS -L/imagemagick/lib" \ - RUSTFLAGS="-L/usr/lib/$TOOL -C link-arg=-Wl,-rpath-link,/usr/lib/$TOOL" \ - IMAGE_MAGICK_LIB_DIRS=/imagemagick/lib \ - IMAGE_MAGICK_INCLUDE_DIRS=/imagemagick/include/ImageMagick-7 \ - PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/imagemagick/lib/pkgconfig \ - CFLAGS="$CFLAGS -I/imagemagick/include/ImageMagick-7" \ - CPPFLAGS="$CPPFLAGS -I/imagemagick/include/ImageMagick-7" + PATH=$PATH:/opt/build/.cargo/bin \ + PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig \ + LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib \ + LD_RUN_PATH=$LD_RUN_PATH:/usr/local/lib \ + LDFLAGS="$LDFLAGS -L/usr/local/lib" \ + IMAGE_MAGICK_LIB_DIRS=/usr/local/lib \ + IMAGE_MAGICK_INCLUDE_DIRS=/usr/local/include/ImageMagick-7 \ + CFLAGS="$CFLAGS -I/usr/local/include/ImageMagick-7" \ + CPPFLAGS="$CPPFLAGS -I/usr/local/include/ImageMagick-7" \ + RUSTFLAGS="-L/usr/lib/$TOOL -C link-arg=-Wl,-rpath-link,/usr/lib/$TOOL -L/usr/$TOOL/lib -C link-arg=-Wl,-rpath-link,/usr/$TOOL/lib" + +COPY --from=rust --chown=build:build /opt/build/.cargo /opt/build/.cargo +COPY --from=rust --chown=build:build /opt/build/.rustup /opt/build/.rustup +COPY --from=imagemagick-builder /usr/local/ /usr/local + +ARG TAG=master +ARG GIT_REPOSITORY=https://git.asonix.dog/asonix/pict-rs + +ADD --chown=build:build $GIT_REPOSITORY/archive/$TAG.tar.gz /opt/build/$TAG.tar.gz + +USER build RUN \ - cargo build --release --target $TARGET && \ - $TOOL-strip target/$TARGET/release/$BINARY + tar zxf $TAG.tar.gz -FROM amd64/ubuntu:20.04 +WORKDIR /opt/build/pict-rs + +RUN \ + USER=build cargo build --target=$TARGET --$BUILD_MODE && \ + $TOOL-strip /opt/build/pict-rs/target/$TARGET/$BUILD_MODE/pict-rs + + +# Producing target binary +FROM target-env -ARG TARGET=x86_64-unknown-linux-gnu ARG UID=991 ARG GID=991 -ARG BINARY=pict-rs RUN \ - apt-get update && \ - apt-get -y upgrade && \ - apt-get -y install \ - tini \ - libgexiv2-2 \ - libgomp1 \ - libltdl7 && \ addgroup --gid $GID pictrs && \ adduser \ --disabled-password \ --gecos "" \ --ingroup pictrs \ --uid $UID \ - --home /opt/pictrs \ + --home /opt/pict-rs \ pictrs && \ - chown -R pictrs:pictrs /mnt + apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y \ + libgexiv2-2 \ + libpng16-16 \ + libjpeg8 \ + libwebp6 \ + libwebpdemux2 \ + libwebpmux3 \ + libltdl7 \ + libgomp1 \ + libxml2 \ + tini -COPY --from=builder /imagemagick /imagemagick -COPY --from=builder /opt/build/repo/target/$TARGET/release/$BINARY /usr/bin/$BINARY +COPY --from=pict-rs-builder /opt/build/pict-rs/target/$TARGET/$BUILD_MODE/pict-rs /usr/local/bin/pict-rs +COPY --from=imagemagick-builder /usr/local /usr/local + +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib VOLUME /mnt -WORKDIR /opt/pictrs +WORKDIR /opt/pict-rs USER pictrs EXPOSE 8080 ENTRYPOINT ["/usr/bin/tini", "--"] -CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080", "-w", "thumbnail"] +CMD ["/usr/local/bin/pict-rs", "-p", "/mnt"] diff --git a/docker/prod/Dockerfile.arm32v7 b/docker/prod/Dockerfile.arm32v7 index c71dde2..462c3c7 100644 --- a/docker/prod/Dockerfile.arm32v7 +++ b/docker/prod/Dockerfile.arm32v7 @@ -1,8 +1,37 @@ -FROM rustembedded/cross:arm-unknown-linux-gnueabihf AS arm32v7-builder +# Target environment +FROM arm32v7/ubuntu:20.04 as target-env + +ENV \ + TARGET=armv7-unknown-linux-gnueabihf \ + BUILD_MODE=release + +# Basic cross-build environment +FROM ubuntu:20.04 as cross-build + +ENV \ + ARCH=armhf \ + HOST=arm-unknown-linux \ + TOOL=arm-linux-gnueabihf \ + TARGET=armv7-unknown-linux-gnueabihf \ + CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc \ + CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc \ + CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++ \ + BUILD_MODE=release + +ENV \ + TOOLCHAIN=stable \ + DEBIAN_FRONTEND=noninteractive \ + PKG_CONFIG_ALLOW_CROSS=1 \ + PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/pkgconfig \ + LD_LIBRARY_PATH=/usr/lib/$TOOL:/usr/$TOOL/lib \ + LD_RUN_PATH=/usr/lib/$TOOL:/usr/$TOOL/lib \ + LDFLAGS="-L/usr/lib/$TOOL -L/usr/$TOOL/lib -Wl,-rpath-link,/usr/lib/$TOOL -Wl,-rpath-link,/usr/$TOOL/lib" \ + CFLAGS="-I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" \ + CPPFLAGS="-I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" RUN \ - apt-get update && \ - apt-get upgrade -y && \ + sed 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch-=amd64,i386] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list > /etc/apt/sources.list.d/ports.list && \ + sed -i 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch=amd64,i386] http:\/\/\1.archive.ubuntu.com\/ubuntu\//g' /etc/apt/sources.list && \ addgroup --gid 991 build && \ adduser \ --disabled-password \ @@ -10,156 +39,169 @@ RUN \ --ingroup build \ --uid 991 \ --home /opt/build \ - build - -ADD https://sh.rustup.rs /opt/build/rustup.sh - -RUN \ - chown -R build:build /opt/build - -USER build -WORKDIR /opt/build - -ENV \ - PATH=$PATH:/opt/build/.cargo/bin \ - TOOLCHAIN=stable \ - HOST=arm-unknown-linux \ - TARGET=arm-unknown-linux-gnueabihf \ - TOOL=arm-linux-gnueabihf \ - ARCH=armhf - -RUN \ - chmod +x rustup.sh && \ - ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ - rustup target add $TARGET - - -FROM arm32v7-builder as builder - -USER root - -ADD https://imagemagick.org/download/ImageMagick.tar.gz ./ - -RUN \ + build && \ dpkg --add-architecture $ARCH && \ apt-get update && \ - apt-get -y install \ - libgexiv2-dev:$ARCH \ + apt-get upgrade -y && \ + apt-get install -y \ + pkg-config \ + build-essential \ + crossbuild-essential-$ARCH + +WORKDIR /opt/build + + +# Environment for ImageMagick +FROM cross-build as imagemagick-builder + +RUN \ + apt-get install -y \ libltdl-dev:$ARCH \ libjpeg-dev:$ARCH \ libpng-dev:$ARCH \ libwebp-dev:$ARCH \ - libxml2-dev:$ARCH \ liblzma-dev:$ARCH \ - libc6-dev-$ARCH-cross \ - llvm-dev \ - libclang-dev \ - clang \ - libexpat1-dev:$ARCH && \ - chown build:build ImageMagick.tar.gz + libxml2-dev:$ARCH +ADD --chown=build:build https://imagemagick.org/download/ImageMagick.tar.gz /opt/build/ImageMagick.tar.gz USER build RUN \ - tar xzf ImageMagick.tar.gz && \ + tar zxf ImageMagick.tar.gz && \ mv ImageMagick-* ImageMagick WORKDIR /opt/build/ImageMagick -ENV \ - USER=build \ - PKG_CONFIG_ALLOW_CROSS=1 \ - PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/pkgconfig \ - LD_LIBRARY_PATH=/usr/lib/$TOOL \ - LD_RUN_PATH=$LD_RUN_PATH:/usr/lib/$TOOL \ - LDFLAGS="$LDFLAGS -L/lib/$TOOL -L/usr/lib/$TOOL -Wl,-rpath-link,/usr/lib/$TOOL" \ - CFLAGS="$CFLAGS -I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" \ - CPPFLAGS="$CPPFLAGS -I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" - RUN \ ./configure \ - CC=$TOOL-gcc \ - CXX=$TOOL-g++ \ - --prefix=/imagemagick \ - --disable-docs \ - --with-modules \ - --enable-shared \ - --disable-static \ - --without-perl \ - --with-xml=yes \ - --with-png=yes \ - --with-jpeg=yes \ - --with-webp=yes \ - --host=$HOST && \ + CC=$TOOL-gcc \ + CXX=$TOOL-g++ \ + --enable-shared \ + --with-modules \ + --disable-static \ + --disable-docs \ + --prefix=/usr/local \ + --with-utilities=no \ + --without-perl \ + --with-xml=yes \ + --with-png=yes \ + --with-jpeg=yes \ + --with-webp=yes \ + --host=$HOST && \ make USER root RUN \ make install && \ - ldconfig /imagemagick/lib && \ - rm -r /usr/include/x86_64-linux-gnu + ldconfig /usr/local/lib + + +# Environment for Rust +FROM cross-build as rust + +RUN \ + apt-get install -y curl + +ENV \ + PATH=$PATH:/opt/build/.cargo/bin + +ADD --chown=build:build https://sh.rustup.rs /opt/build/rustup.sh USER build -WORKDIR /opt/build +RUN \ + chmod +x rustup.sh && \ + ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ + rustup target add $TARGET -ARG TAG=master -ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs -ARG BINARY=pict-rs +USER root + + +# Environment for pict-rs +FROM cross-build as pict-rs-builder RUN \ - git clone -b $TAG $REPOSITORY repo - -WORKDIR /opt/build/repo + apt-get install -y \ + libgexiv2-dev:$ARCH \ + libxml2:$ARCH \ + libltdl7:$ARCH \ + llvm-dev \ + libclang-dev \ + clang && \ + rm -rf /usr/include/x86_64-linux-gnu ENV \ - LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/imagemagick/lib \ - LD_RUN_PATH=$LD_RUN_PATH:/imagemagick/lib \ - LDFLAGS="$LDFLAGS -L/imagemagick/lib" \ - RUSTFLAGS="-L/usr/lib/$TOOL -L/lib/$TOOL -C link-arg=-Wl,-rpath-link,/usr/lib/$TOOL -C link-arg=-Wl,-rpath-link,/lib/$TOOL" \ - IMAGE_MAGICK_LIB_DIRS=/imagemagick/lib \ - IMAGE_MAGICK_INCLUDE_DIRS=/imagemagick/include/ImageMagick-7 \ - PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/imagemagick/lib/pkgconfig \ - CFLAGS="$CFLAGS -I/imagemagick/include/ImageMagick-7" \ - CPPFLAGS="$CPPFLAGS -I/imagemagick/include/ImageMagick-7" + PATH=$PATH:/opt/build/.cargo/bin \ + PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig \ + LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib \ + LD_RUN_PATH=$LD_RUN_PATH:/usr/local/lib \ + LDFLAGS="$LDFLAGS -L/usr/local/lib" \ + IMAGE_MAGICK_LIB_DIRS=/usr/local/lib \ + IMAGE_MAGICK_INCLUDE_DIRS=/usr/local/include/ImageMagick-7 \ + CFLAGS="$CFLAGS -I/usr/local/include/ImageMagick-7" \ + CPPFLAGS="$CPPFLAGS -I/usr/local/include/ImageMagick-7" \ + RUSTFLAGS="-L/usr/lib/$TOOL -C link-arg=-Wl,-rpath-link,/usr/lib/$TOOL -L/usr/$TOOL/lib -C link-arg=-Wl,-rpath-link,/usr/$TOOL/lib" + +COPY --from=rust --chown=build:build /opt/build/.cargo /opt/build/.cargo +COPY --from=rust --chown=build:build /opt/build/.rustup /opt/build/.rustup +COPY --from=imagemagick-builder /usr/local/ /usr/local + +ARG TAG=master +ARG GIT_REPOSITORY=https://git.asonix.dog/asonix/pict-rs + +ADD --chown=build:build $GIT_REPOSITORY/archive/$TAG.tar.gz /opt/build/$TAG.tar.gz + +USER build RUN \ - cargo build --release --target $TARGET && \ - $TOOL-strip target/$TARGET/release/$BINARY + tar zxf $TAG.tar.gz -FROM arm32v7/ubuntu:20.04 +WORKDIR /opt/build/pict-rs + +RUN \ + USER=build cargo build --target=$TARGET --$BUILD_MODE && \ + $TOOL-strip /opt/build/pict-rs/target/$TARGET/$BUILD_MODE/pict-rs + + +# Producing target binary +FROM target-env -ARG TARGET=arm-unknown-linux-gnueabihf ARG UID=991 ARG GID=991 -ARG BINARY=pict-rs RUN \ - apt-get update && \ - apt-get -y upgrade && \ - apt-get -y install \ - tini \ - libgexiv2-2 \ - libgomp1 \ - libltdl7 && \ addgroup --gid $GID pictrs && \ adduser \ --disabled-password \ --gecos "" \ --ingroup pictrs \ --uid $UID \ - --home /opt/pictrs \ + --home /opt/pict-rs \ pictrs && \ - chown -R pictrs:pictrs /mnt + apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y \ + libgexiv2-2 \ + libpng16-16 \ + libjpeg8 \ + libwebp6 \ + libwebpdemux2 \ + libwebpmux3 \ + libltdl7 \ + libgomp1 \ + libxml2 \ + tini -COPY --from=builder /imagemagick /imagemagick -COPY --from=builder /opt/build/repo/target/$TARGET/release/$BINARY /usr/bin/$BINARY +COPY --from=pict-rs-builder /opt/build/pict-rs/target/$TARGET/$BUILD_MODE/pict-rs /usr/local/bin/pict-rs +COPY --from=imagemagick-builder /usr/local /usr/local + +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib VOLUME /mnt -WORKDIR /opt/pictrs +WORKDIR /opt/pict-rs USER pictrs EXPOSE 8080 ENTRYPOINT ["/usr/bin/tini", "--"] -CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080", "-w", "thumbnail"] +CMD ["/usr/local/bin/pict-rs", "-p", "/mnt"] diff --git a/docker/prod/Dockerfile.arm64v8 b/docker/prod/Dockerfile.arm64v8 index 39395e1..d9f2212 100644 --- a/docker/prod/Dockerfile.arm64v8 +++ b/docker/prod/Dockerfile.arm64v8 @@ -1,8 +1,37 @@ -FROM rustembedded/cross:aarch64-unknown-linux-gnu AS aarch64-builder +# Target environment +FROM arm64v8/ubuntu:20.04 as target-env + +ENV \ + TARGET=aarch64-unknown-linux-gnu \ + BUILD_MODE=release + +# Basic cross-build environment +FROM ubuntu:20.04 as cross-build + +ENV \ + ARCH=arm64 \ + HOST=aarch64-unknown-linux \ + TOOL=aarch64-linux-gnu \ + TARGET=aarch64-unknown-linux-gnu \ + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ + CC_AARCH64_UNKNOWN_LINUX_GNU=aarch64-linux-gnu-gcc \ + CXX_AARCH64_UNKNOWN_LINUX_GNU=aarch64-linux-gnu-g++ \ + BUILD_MODE=release + +ENV \ + TOOLCHAIN=stable \ + DEBIAN_FRONTEND=noninteractive \ + PKG_CONFIG_ALLOW_CROSS=1 \ + PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/pkgconfig \ + LD_LIBRARY_PATH=/usr/lib/$TOOL:/usr/$TOOL/lib \ + LD_RUN_PATH=/usr/lib/$TOOL:/usr/$TOOL/lib \ + LDFLAGS="-L/usr/lib/$TOOL -L/usr/$TOOL/lib -Wl,-rpath-link,/usr/lib/$TOOL -Wl,-rpath-link,/usr/$TOOL/lib" \ + CFLAGS="-I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" \ + CPPFLAGS="-I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" RUN \ - apt-get update && \ - apt-get upgrade -y && \ + sed 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch-=amd64,i386] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list > /etc/apt/sources.list.d/ports.list && \ + sed -i 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch=amd64,i386] http:\/\/\1.archive.ubuntu.com\/ubuntu\//g' /etc/apt/sources.list && \ addgroup --gid 991 build && \ adduser \ --disabled-password \ @@ -10,155 +39,169 @@ RUN \ --ingroup build \ --uid 991 \ --home /opt/build \ - build - -ADD https://sh.rustup.rs /opt/build/rustup.sh - -RUN \ - chown -R build:build /opt/build - -USER build -WORKDIR /opt/build - -ENV \ - PATH=$PATH:/opt/build/.cargo/bin \ - TOOLCHAIN=stable \ - HOST=aarch64-unknown-linux \ - TARGET=aarch64-unknown-linux-gnu \ - TOOL=aarch64-linux-gnu \ - ARCH=arm64 - -RUN \ - chmod +x rustup.sh && \ - ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ - rustup target add $TARGET - - -FROM aarch64-builder as builder - -USER root - -ADD https://imagemagick.org/download/ImageMagick.tar.gz ./ - -RUN \ + build && \ dpkg --add-architecture $ARCH && \ apt-get update && \ - apt-get -y install \ - libgexiv2-dev:$ARCH \ + apt-get upgrade -y && \ + apt-get install -y \ + pkg-config \ + build-essential \ + crossbuild-essential-$ARCH + +WORKDIR /opt/build + + +# Environment for ImageMagick +FROM cross-build as imagemagick-builder + +RUN \ + apt-get install -y \ libltdl-dev:$ARCH \ libjpeg-dev:$ARCH \ libpng-dev:$ARCH \ libwebp-dev:$ARCH \ - libxml2-dev:$ARCH \ liblzma-dev:$ARCH \ - libc6-dev-$ARCH-cross \ - llvm-dev \ - libclang-dev \ - clang && \ - chown build:build ImageMagick.tar.gz + libxml2-dev:$ARCH +ADD --chown=build:build https://imagemagick.org/download/ImageMagick.tar.gz /opt/build/ImageMagick.tar.gz USER build RUN \ - tar xzf ImageMagick.tar.gz && \ + tar zxf ImageMagick.tar.gz && \ mv ImageMagick-* ImageMagick WORKDIR /opt/build/ImageMagick -ENV \ - USER=build \ - PKG_CONFIG_ALLOW_CROSS=1 \ - PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/pkgconfig \ - LD_LIBRARY_PATH=/usr/lib/$TOOL \ - LD_RUN_PATH=$LD_RUN_PATH:/usr/lib/$TOOL \ - LDFLAGS="$LDFLAGS -L/usr/lib/$TOOL -Wl,-rpath-link,/usr/lib/$TOOL" \ - CFLAGS="$CFLAGS -I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" \ - CPPFLAGS="$CPPFLAGS -I/usr/include/$TOOL -I/usr/$TOOL/include -I/usr/include" - RUN \ ./configure \ - CC=$TOOL-gcc \ - CXX=$TOOL-g++ \ - --prefix=/imagemagick \ - --disable-docs \ - --with-modules \ - --enable-shared \ - --disable-static \ - --without-perl \ - --with-xml=yes \ - --with-png=yes \ - --with-jpeg=yes \ - --with-webp=yes \ - --host=$HOST && \ + CC=$TOOL-gcc \ + CXX=$TOOL-g++ \ + --enable-shared \ + --with-modules \ + --disable-static \ + --disable-docs \ + --prefix=/usr/local \ + --with-utilities=no \ + --without-perl \ + --with-xml=yes \ + --with-png=yes \ + --with-jpeg=yes \ + --with-webp=yes \ + --host=$HOST && \ make USER root RUN \ make install && \ - ldconfig /imagemagick/lib && \ - rm -r /usr/include/x86_64-linux-gnu + ldconfig /usr/local/lib + + +# Environment for Rust +FROM cross-build as rust + +RUN \ + apt-get install -y curl + +ENV \ + PATH=$PATH:/opt/build/.cargo/bin + +ADD --chown=build:build https://sh.rustup.rs /opt/build/rustup.sh USER build -WORKDIR /opt/build +RUN \ + chmod +x rustup.sh && \ + ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ + rustup target add $TARGET -ARG TAG=master -ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs -ARG BINARY=pict-rs +USER root + + +# Environment for pict-rs +FROM cross-build as pict-rs-builder RUN \ - git clone -b $TAG $REPOSITORY repo - -WORKDIR /opt/build/repo + apt-get install -y \ + libgexiv2-dev:$ARCH \ + libxml2:$ARCH \ + libltdl7:$ARCH \ + llvm-dev \ + libclang-dev \ + clang && \ + rm -rf /usr/include/x86_64-linux-gnu ENV \ - LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/imagemagick/lib \ - LD_RUN_PATH=$LD_RUN_PATH:/imagemagick/lib \ - LDFLAGS="$LDFLAGS -L/imagemagick/lib" \ - RUSTFLAGS="-L/usr/lib/$TOOL -C link-arg=-Wl,-rpath-link,/usr/lib/$TOOL" \ - IMAGE_MAGICK_LIB_DIRS=/imagemagick/lib \ - IMAGE_MAGICK_INCLUDE_DIRS=/imagemagick/include/ImageMagick-7 \ - PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/imagemagick/lib/pkgconfig \ - CFLAGS="$CFLAGS -I/imagemagick/include/ImageMagick-7" \ - CPPFLAGS="$CPPFLAGS -I/imagemagick/include/ImageMagick-7" + PATH=$PATH:/opt/build/.cargo/bin \ + PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig \ + LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib \ + LD_RUN_PATH=$LD_RUN_PATH:/usr/local/lib \ + LDFLAGS="$LDFLAGS -L/usr/local/lib" \ + IMAGE_MAGICK_LIB_DIRS=/usr/local/lib \ + IMAGE_MAGICK_INCLUDE_DIRS=/usr/local/include/ImageMagick-7 \ + CFLAGS="$CFLAGS -I/usr/local/include/ImageMagick-7" \ + CPPFLAGS="$CPPFLAGS -I/usr/local/include/ImageMagick-7" \ + RUSTFLAGS="-L/usr/lib/$TOOL -C link-arg=-Wl,-rpath-link,/usr/lib/$TOOL -L/usr/$TOOL/lib -C link-arg=-Wl,-rpath-link,/usr/$TOOL/lib" + +COPY --from=rust --chown=build:build /opt/build/.cargo /opt/build/.cargo +COPY --from=rust --chown=build:build /opt/build/.rustup /opt/build/.rustup +COPY --from=imagemagick-builder /usr/local/ /usr/local + +ARG TAG=master +ARG GIT_REPOSITORY=https://git.asonix.dog/asonix/pict-rs + +ADD --chown=build:build $GIT_REPOSITORY/archive/$TAG.tar.gz /opt/build/$TAG.tar.gz + +USER build RUN \ - cargo build --release --target $TARGET && \ - $TOOL-strip target/$TARGET/release/$BINARY + tar zxf $TAG.tar.gz -FROM arm64v8/ubuntu:20.04 +WORKDIR /opt/build/pict-rs + +RUN \ + USER=build cargo build --target=$TARGET --$BUILD_MODE && \ + $TOOL-strip /opt/build/pict-rs/target/$TARGET/$BUILD_MODE/pict-rs + + +# Producing target binary +FROM target-env -ARG TARGET=aarch64-unknown-linux-gnu ARG UID=991 ARG GID=991 -ARG BINARY=pict-rs RUN \ - apt-get update && \ - apt-get -y upgrade && \ - apt-get -y install \ - tini \ - libgexiv2-2 \ - libgomp1 \ - libltdl7 && \ addgroup --gid $GID pictrs && \ adduser \ --disabled-password \ --gecos "" \ --ingroup pictrs \ --uid $UID \ - --home /opt/pictrs \ + --home /opt/pict-rs \ pictrs && \ - chown -R pictrs:pictrs /mnt + apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y \ + libgexiv2-2 \ + libpng16-16 \ + libjpeg8 \ + libwebp6 \ + libwebpdemux2 \ + libwebpmux3 \ + libltdl7 \ + libgomp1 \ + libxml2 \ + tini -COPY --from=builder /imagemagick /imagemagick -COPY --from=builder /opt/build/repo/target/$TARGET/release/$BINARY /usr/bin/$BINARY +COPY --from=pict-rs-builder /opt/build/pict-rs/target/$TARGET/$BUILD_MODE/pict-rs /usr/local/bin/pict-rs +COPY --from=imagemagick-builder /usr/local /usr/local + +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib VOLUME /mnt -WORKDIR /opt/pictrs +WORKDIR /opt/pict-rs USER pictrs EXPOSE 8080 ENTRYPOINT ["/usr/bin/tini", "--"] -CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080", "-w", "thumbnail"] +CMD ["/usr/local/bin/pict-rs", "-p", "/mnt"] diff --git a/src/config.rs b/src/config.rs index c6501e4..8961035 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,7 +30,7 @@ pub(crate) struct Config { short, long, env = "PICTRS_FORMAT", - help = "An optional image format to convert all uploaded files into, supports 'jpg' and 'png'" + help = "An optional image format to convert all uploaded files into, supports 'jpg', 'png', and 'webp'" )] format: Option, @@ -88,6 +88,7 @@ pub(crate) struct FormatError(String); pub(crate) enum Format { Jpeg, Png, + Webp, } impl std::str::FromStr for Format { @@ -97,6 +98,7 @@ impl std::str::FromStr for Format { match s { "png" => Ok(Format::Png), "jpg" => Ok(Format::Jpeg), + "webp" => Ok(Format::Webp), other => Err(FormatError(other.to_string())), } } diff --git a/src/error.rs b/src/error.rs index 676b33b..5af73b0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -68,6 +68,9 @@ pub(crate) enum UploadError { #[error("File metadata could not be parsed, {0}")] Validate(#[from] rexiv2::Rexiv2Error), + + #[error("Error in MagickWand, {0}")] + Wand(String), } impl From for UploadError { diff --git a/src/main.rs b/src/main.rs index a6cbfaf..d6e8343 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ mod validate; use self::{ config::Config, error::UploadError, middleware::Tracing, upload_manager::UploadManager, + validate::image_webp, }; const MEGABYTES: usize = 1024 * 1024; @@ -75,7 +76,7 @@ fn to_ext(mime: mime::Mime) -> &'static str { } else if mime == mime::IMAGE_GIF { ".gif" } else { - ".bmp" + ".webp" } } @@ -84,7 +85,7 @@ fn from_ext(ext: std::ffi::OsString) -> mime::Mime { Some("png") => mime::IMAGE_PNG, Some("jpg") => mime::IMAGE_JPEG, Some("gif") => mime::IMAGE_GIF, - _ => mime::IMAGE_BMP, + _ => image_webp(), } } diff --git a/src/validate.rs b/src/validate.rs index acf7223..c480c20 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1,13 +1,37 @@ use crate::{config::Format, error::UploadError, upload_manager::tmp_file}; use actix_web::web; -use image::{io::Reader, ImageDecoder, ImageEncoder, ImageFormat}; +use image::{io::Reader, ImageFormat}; +use magick_rust::MagickWand; use rexiv2::{MediaType, Metadata}; use std::{ fs::File, io::{BufReader, BufWriter, Write}, path::PathBuf, }; -use tracing::{debug, instrument, trace, Span}; +use tracing::{debug, error, instrument, trace, warn, Span}; + +pub(crate) trait Op { + fn op(&self, f: F) -> Result + where + F: Fn(&Self) -> Result; +} + +impl Op for MagickWand { + fn op(&self, f: F) -> Result + where + F: Fn(&Self) -> Result, + { + match f(self) { + Ok(t) => Ok(t), + Err(e) => { + if let Ok(e) = self.get_exception() { + error!("WandError: {}", e.0); + } + Err(UploadError::Wand(e.to_owned())) + } + } + } +} #[derive(Debug, thiserror::Error)] pub(crate) enum GifError { @@ -18,12 +42,21 @@ pub(crate) enum GifError { Io(#[from] std::io::Error), } +pub(crate) fn image_webp() -> mime::Mime { + "image/webp".parse().unwrap() +} + +fn ptos(p: &PathBuf) -> Result { + Ok(p.to_str().ok_or(UploadError::Path)?.to_owned()) +} + // import & export image using the image crate #[instrument] pub(crate) async fn validate_image( tmpfile: PathBuf, prescribed_format: Option, ) -> Result { + let tmpfile_str = ptos(&tmpfile)?; let span = Span::current(); let content_type = web::block(move || { @@ -39,21 +72,54 @@ pub(crate) async fn validate_image( mime::IMAGE_GIF } (Some(Format::Jpeg), MediaType::Jpeg) | (None, MediaType::Jpeg) => { - validate(&tmpfile, ImageFormat::Jpeg)?; + { + let wand = MagickWand::new(); + debug!("reading: {}", tmpfile_str); + wand.op(|w| w.read_image(&tmpfile_str))?; + debug!("format: {}", wand.op(|w| w.get_format())?); + } + + let meta = Metadata::new_from_path(&tmpfile)?; meta.clear(); meta.save_to_file(&tmpfile)?; mime::IMAGE_JPEG } (Some(Format::Png), MediaType::Png) | (None, MediaType::Png) => { - validate(&tmpfile, ImageFormat::Png)?; + { + let wand = MagickWand::new(); + debug!("reading: {}", tmpfile_str); + wand.op(|w| w.read_image(&tmpfile_str))?; + debug!("format: {}", wand.op(|w| w.get_format())?); + } + + let meta = Metadata::new_from_path(&tmpfile)?; meta.clear(); meta.save_to_file(&tmpfile)?; mime::IMAGE_PNG } + (Some(Format::Webp), MediaType::Other(webp)) | (None, MediaType::Other(webp)) + if webp == "image/webp" => + { + { + let wand = MagickWand::new(); + debug!("reading: {}", tmpfile_str); + wand.op(|w| w.read_image(&tmpfile_str))?; + + debug!("format: {}", wand.op(|w| w.get_format())?); + debug!("type: {}", wand.op(|w| Ok(w.get_type()))?); + debug!("image_type: {}", wand.op(|w| Ok(w.get_image_type()))?); + } + + // let meta = Metadata::new_from_path(&tmpfile)?; + // meta.clear(); + // meta.save_to_file(&tmpfile)?; + + image_webp() + } (Some(Format::Jpeg), _) => { let newfile = tmp_file(); convert(&tmpfile, &newfile, ImageFormat::Jpeg)?; @@ -66,13 +132,10 @@ pub(crate) async fn validate_image( mime::IMAGE_PNG } - (_, MediaType::Bmp) => { - let newfile = tmp_file(); - validate_bmp(&tmpfile, &newfile)?; - - mime::IMAGE_BMP + (_, media_type) => { + warn!("Unsupported media type, {}", media_type); + return Err(UploadError::UnsupportedFormat); } - _ => return Err(UploadError::UnsupportedFormat), }; drop(entered); @@ -112,20 +175,6 @@ fn validate(path: &PathBuf, format: ImageFormat) -> Result<(), UploadError> { Ok(()) } -#[instrument] -fn validate_bmp(from: &PathBuf, to: &PathBuf) -> Result<(), UploadError> { - debug!("Transmuting BMP"); - let decoder = image::bmp::BmpDecoder::new(BufReader::new(File::open(from)?))?; - - let mut writer = BufWriter::new(File::create(to)?); - let encoder = image::bmp::BMPEncoder::new(&mut writer); - validate_still_image(decoder, encoder)?; - - writer.flush()?; - std::fs::rename(to, from)?; - Ok(()) -} - #[instrument] fn validate_gif(from: &PathBuf, to: &PathBuf) -> Result<(), GifError> { debug!("Transmuting GIF"); @@ -157,21 +206,3 @@ fn validate_gif(from: &PathBuf, to: &PathBuf) -> Result<(), GifError> { std::fs::rename(to, from)?; Ok(()) } - -fn validate_still_image<'a, D, E>(decoder: D, encoder: E) -> Result<(), UploadError> -where - D: ImageDecoder<'a>, - E: ImageEncoder, -{ - let (width, height) = decoder.dimensions(); - let color_type = decoder.color_type(); - let total_bytes = decoder.total_bytes(); - debug!("Reading image"); - let mut decoded_bytes = vec![0u8; total_bytes as usize]; - decoder.read_image(&mut decoded_bytes)?; - - debug!("Writing image"); - encoder.write_image(&decoded_bytes, width, height, color_type)?; - - Ok(()) -}