From 4750fdfebee05d424b6e30cbf711326cc43b5fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emrik=20=C3=96stling?= Date: Wed, 23 Sep 2026 23:27:40 +0200 Subject: [PATCH] feature: support PUID/PGID to change user (#655) --- Dockerfile | 22 +++++++++++++++------- README.md | 10 +++++++--- compose.yaml | 3 +++ entrypoint.sh | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 10 deletions(-) create mode 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 5dc213d..45d858e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,9 +12,9 @@ RUN apt-get update && apt-get install -y \ # if architecture is arm64, use the arm64 version of bun RUN ARCH=$(uname -m) && \ if [ "$ARCH" = "aarch64" ]; then \ - curl -fsSL -o bun-linux-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.4.2/bun-linux-aarch64.zip; \ + curl -fsSL -o bun-linux-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.4.2/bun-linux-aarch64.zip; \ else \ - curl -fsSL -o bun-linux-x64-baseline.zip https://github.com/oven-sh/bun/releases/download/bun-v1.4.2/bun-linux-x64-baseline.zip; \ + curl -fsSL -o bun-linux-x64-baseline.zip https://github.com/oven-sh/bun/releases/download/bun-v1.4.2/bun-linux-x64-baseline.zip; \ fi RUN unzip -j bun-linux-*.zip -d /usr/local/bin && \ @@ -45,6 +45,9 @@ RUN bun run build FROM base AS release # install additional dependencies +ENV PIPX_HOME=/opt/pipx +ENV PIPX_BIN_DIR=/usr/local/bin + RUN apt-get update && apt-get install -y \ assimp-utils \ calibre \ @@ -53,6 +56,7 @@ RUN apt-get update && apt-get install -y \ dvisvgm \ ffmpeg \ ghostscript \ + gosu \ graphicsmagick \ imagemagick-7.q16 \ inkscape \ @@ -84,15 +88,15 @@ RUN apt-get update && apt-get install -y \ && pipx install "markitdown[all]" \ && rm -rf /var/lib/apt/lists/* -# Add pipx bin directory to PATH -ENV PATH="/root/.local/bin:${PATH}" +RUN groupadd -g 1000 convertx && \ + useradd -u 1000 -g convertx -d /home/convertx -m -s /bin/bash convertx # Install VTracer binary RUN ARCH=$(uname -m) && \ if [ "$ARCH" = "aarch64" ]; then \ - VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \ + VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \ else \ - VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \ + VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \ fi && \ curl -L -o /tmp/vtracer.tar.gz "https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \ tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \ @@ -107,8 +111,12 @@ COPY --from=prerelease /app/dist /app/dist # COPY . . RUN mkdir data +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + EXPOSE 3000/tcp # used for calibre ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox" ENV NODE_ENV=production -ENTRYPOINT [ "bun", "run", "dist/src/index.js" ] +ENTRYPOINT [ "/entrypoint.sh" ] +CMD [ "bun", "run", "dist/src/index.js" ] diff --git a/README.md b/README.md index 12aa61a..cac00d6 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ services: ports: - "3000:3000" environment: + - PUID=1000 # user id for file permissions (should match your host user id) - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() if unset # - HTTP_ALLOWED=true # uncomment this if accessing it over a non-https connection volumes: @@ -77,12 +78,12 @@ services: or ```bash -docker run -p 3000:3000 -v ./data:/app/data ghcr.io/c4illin/convertx +docker run -p 3000:3000 -e PUID=1000 -e PGID=1000 -v ./data:/app/data ghcr.io/c4illin/convertx ``` -Then visit `http://localhost:3000` in your browser and create your account. Don't leave it unconfigured and open, as anyone can register the first account. +Ensure `PUID` and `PGID` match your host user (`id -u` and `id -g`) to avoid permission issues on the mapped `./data` volume. If you get unable to open database file, verify the directory permissions or run `chown -R $USER:$USER path` on the path you choose. -If you get unable to open database file run `chown -R $USER:$USER path` on the path you choose. +Then visit `http://localhost:3000` in your browser and create your account. Don't leave it unconfigured and open, as anyone can register the first account. ### Environment variables @@ -91,6 +92,9 @@ All are optional, JWT_SECRET is recommended to be set. | Name | Default | Description | | ---------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | JWT_SECRET | when unset it will use the value from randomUUID() | A long and secret string used to sign the JSON Web Token | +| PUID | 0 (root) | User ID to run the application process as (it first uses root to chown the files) | +| PGID | PUID if unset, root if both unset | Group ID to run the application process as | +| UMASK | 002 (when PUID is set) | File creation mask for processes and created files | | ACCOUNT_REGISTRATION | false | Allow users to register accounts | | HTTP_ALLOWED | false | Allow HTTP connections, only set this to true locally | | ALLOW_UNAUTHENTICATED | false | Allow unauthenticated users to use the service, only set this to true locally | diff --git a/compose.yaml b/compose.yaml index 2af5058..1bb0d99 100644 --- a/compose.yaml +++ b/compose.yaml @@ -8,6 +8,9 @@ services: volumes: - ./data:/app/data environment: # Defaults are listed below. All are optional. + - PUID=1000 # user ID for file permissions, defaults to 0 + - PGID=1000 # group ID for file permissions, defaults to 0 + - UMASK=002 # file creation mask, defaults to 002 when PUID is set - ACCOUNT_REGISTRATION=true # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account) - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default - HTTP_ALLOWED=false # setting this to true is unsafe, only set this to true locally diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..520ad7b --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -e + +if ! [[ "$PUID" =~ ^[0-9]+$ ]] || ! [[ "$PGID" =~ ^[0-9]+$ ]]; then + echo "[entrypoint] ERROR: PUID and PGID must be numeric integers." >&2 + exit 1 +fi + +# If PUID is not set or is 0, run as root (backwards-compatible default) +if [ -z "$PUID" ] || [ "$PUID" -eq 0 ]; then + if [ -n "$UMASK" ]; then + umask "$UMASK" + fi + if [ $# -eq 0 ]; then + set -- bun run dist/src/index.js + fi + exec "$@" +fi + +PGID=${PGID:-$PUID} +UMASK=${UMASK:-002} + +umask "$UMASK" + +if [ "$PGID" -ne "$(id -g convertx)" ]; then + groupmod -o -g "$PGID" convertx +fi + +if [ "$PUID" -ne "$(id -u convertx)" ]; then + usermod -o -u "$PUID" convertx +fi + +# Ensure home directory exists and is owned by convertx +mkdir -p /home/convertx +if [ "$(stat -c '%u:%g' /home/convertx)" != "$PUID:$PGID" ]; then + chown -R convertx:convertx /home/convertx +fi + +# Ensure /app/data exists and fix permissions only if the mount point ownership differs +mkdir -p /app/data +if [ "$(stat -c '%u:%g' /app/data)" != "$PUID:$PGID" ]; then + find /app/data \( ! -user "$PUID" -o ! -group "$PGID" \) -exec chown -h "$PUID:$PGID" '{}' + +fi + +export HOME=/home/convertx + +if [ $# -eq 0 ]; then + set -- bun run dist/src/index.js +fi + +exec gosu convertx "$@" +