mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
unoserver docker (#6328)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details.
This commit is contained in:
@@ -97,7 +97,7 @@ To add a 3rd UNO server:
|
||||
```yaml
|
||||
unoserver3:
|
||||
container_name: UNO-Server-3
|
||||
image: ghcr.io/unoconv/unoserver-docker:0.4.4
|
||||
image: ghcr.io/stirling-tools/stirling-unoserver:latest
|
||||
# ... same config as unoserver1/2
|
||||
```
|
||||
|
||||
|
||||
@@ -57,38 +57,34 @@ services:
|
||||
|
||||
unoserver1:
|
||||
container_name: UNO-Server-1
|
||||
# NOTE: This image needs to be updated to unoserver 3.6 to match Stirling-PDF's client version
|
||||
# Current :latest uses 3.4 which causes API mismatch errors
|
||||
image: ghcr.io/unoconv/unoserver-docker:latest
|
||||
# See docker/unoserver/VERSION for available tags.
|
||||
image: ghcr.io/stirling-tools/stirling-unoserver:latest
|
||||
environment:
|
||||
UNOSERVER_PORT: "2003"
|
||||
UNOSERVER_UNO_PORT: "2002"
|
||||
UNOSERVER_CONVERSION_TIMEOUT: "1800"
|
||||
# Recycle hourly to bound LibreOffice memory; 0 disables, floor 60s.
|
||||
UNOSERVER_RECYCLE_INTERVAL_SECONDS: "3600"
|
||||
volumes:
|
||||
- stirling-tmp:/tmp/stirling-pdf:rw
|
||||
expose:
|
||||
- "2003"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "timeout 2 bash -c 'cat < /dev/null > /dev/tcp/localhost/2003' || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- stirling-network
|
||||
restart: on-failure:5
|
||||
|
||||
unoserver2:
|
||||
container_name: UNO-Server-2
|
||||
# NOTE: This image needs to be updated to unoserver 3.6 to match Stirling-PDF's client version
|
||||
# Current :latest uses 3.4 which causes API mismatch errors
|
||||
image: ghcr.io/unoconv/unoserver-docker:latest
|
||||
image: ghcr.io/stirling-tools/stirling-unoserver:latest
|
||||
environment:
|
||||
UNOSERVER_PORT: "2003"
|
||||
UNOSERVER_UNO_PORT: "2002"
|
||||
UNOSERVER_CONVERSION_TIMEOUT: "1800"
|
||||
UNOSERVER_RECYCLE_INTERVAL_SECONDS: "3600"
|
||||
volumes:
|
||||
- stirling-tmp:/tmp/stirling-pdf:rw
|
||||
expose:
|
||||
- "2003"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "timeout 2 bash -c 'cat < /dev/null > /dev/tcp/localhost/2003' || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- stirling-network
|
||||
restart: on-failure:5
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# Standalone unoserver image for Stirling-PDF remote UNO mode.
|
||||
# Pinned to unoserver 3.6 to match Stirling-PDF's client (avoids 3.4/3.6 wire mismatch).
|
||||
|
||||
FROM ubuntu:noble@sha256:c4a8d5503dfb2a3eb8ab5f807da5bc69a85730fb49b5cfca2330194ebcc41c7b
|
||||
|
||||
ARG UNOSERVER_VERSION=3.6
|
||||
# ~120 MB of CJK fonts — opt-in.
|
||||
ARG INSTALL_CJK_FONTS=false
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8 \
|
||||
UNOSERVER_PORT=2003 \
|
||||
UNOSERVER_UNO_PORT=2002 \
|
||||
UNOSERVER_INTERFACE=0.0.0.0 \
|
||||
UNOSERVER_CONVERSION_TIMEOUT=1800 \
|
||||
UNOSERVER_RECYCLE_INTERVAL_SECONDS=0 \
|
||||
UNOSERVER_PROFILE_DIR=/var/lib/unoserver/profile
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
software-properties-common ca-certificates curl gnupg tini; \
|
||||
add-apt-repository -y ppa:libreoffice/ppa; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3 python3-venv python3-uno python3-packaging \
|
||||
libreoffice-writer-nogui libreoffice-calc-nogui \
|
||||
libreoffice-impress-nogui libreoffice-draw-nogui \
|
||||
libreoffice-base-nogui libreoffice-java-common \
|
||||
xvfb x11-utils coreutils \
|
||||
fonts-dejavu fonts-liberation fonts-liberation2 \
|
||||
fonts-crosextra-caladea fonts-crosextra-carlito \
|
||||
fonts-noto-core fonts-noto-mono fonts-noto-color-emoji \
|
||||
hyphen-en-gb hyphen-en-us hyphen-fr hyphen-de hyphen-es hyphen-it \
|
||||
hyphen-pt-pt hyphen-pt-br hyphen-nl hyphen-pl hyphen-ru \
|
||||
fontconfig \
|
||||
libcairo2 libpango-1.0-0 libpangoft2-1.0-0 libgdk-pixbuf-2.0-0 \
|
||||
libfreetype6 libfontconfig1 libx11-6 libxt6 libxext6 libxrender1 \
|
||||
libxtst6 libxi6 libxinerama1 libxkbcommon0 libsm6 libice6 \
|
||||
libglib2.0-0; \
|
||||
if [ "${INSTALL_CJK_FONTS}" = "true" ]; then \
|
||||
apt-get install -y --no-install-recommends fonts-noto-cjk; \
|
||||
fi; \
|
||||
apt-get remove --purge -y software-properties-common; \
|
||||
apt-get autoremove --purge -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||
|
||||
# Noble's hyphen-* packages skip LibreOffice registration; wire it up by hand
|
||||
# or the dicts never load.
|
||||
RUN set -eu; \
|
||||
mkdir -p /usr/share/myspell/dicts /usr/share/myspell/infos/ooo; \
|
||||
for f in /usr/share/hyphen/hyph_*_*.dic; do \
|
||||
base=$(basename "$f" .dic); \
|
||||
locale=${base#hyph_}; \
|
||||
lang=${locale%_*}; \
|
||||
country=${locale#*_}; \
|
||||
ln -sfn "../../hyphen/${base}.dic" "/usr/share/myspell/dicts/${base}.dic"; \
|
||||
printf "HYPH %s %s %s\n" "$lang" "$country" "$base" \
|
||||
> "/usr/share/myspell/infos/ooo/hyphen-${lang}-${country}"; \
|
||||
done
|
||||
|
||||
COPY --chmod=0644 docker/unoserver/fonts.conf /etc/fonts/conf.d/100-stirling.conf
|
||||
RUN fc-cache -f
|
||||
|
||||
# venv with --system-site-packages so it picks up distro python3-uno.
|
||||
RUN python3 -m venv /opt/unoserver-venv --system-site-packages && \
|
||||
/opt/unoserver-venv/bin/pip install --no-cache-dir "unoserver==${UNOSERVER_VERSION}" && \
|
||||
ln -sf /opt/unoserver-venv/bin/unoserver /usr/local/bin/unoserver && \
|
||||
ln -sf /opt/unoserver-venv/bin/unoconvert /usr/local/bin/unoconvert && \
|
||||
ln -sf /opt/unoserver-venv/bin/unoping /usr/local/bin/unoping && \
|
||||
find /opt/unoserver-venv -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
||||
|
||||
RUN rm -rf /usr/lib/libreoffice/share/gallery \
|
||||
/usr/lib/libreoffice/share/template \
|
||||
/usr/lib/libreoffice/share/wizards \
|
||||
/usr/lib/libreoffice/help \
|
||||
/usr/lib/libreoffice/share/extensions/wiki-publisher \
|
||||
/usr/lib/libreoffice/share/extensions/nlpsolver \
|
||||
/usr/share/doc/* /usr/share/man/* /usr/share/info/*
|
||||
|
||||
ARG PUID=1001
|
||||
ARG PGID=1001
|
||||
RUN groupadd -g "${PGID}" unoserver && \
|
||||
useradd -u "${PUID}" -g unoserver -m -d /home/unoserver -s /bin/bash unoserver && \
|
||||
mkdir -p "${UNOSERVER_PROFILE_DIR}" && \
|
||||
chown -R unoserver:unoserver "${UNOSERVER_PROFILE_DIR}" /home/unoserver
|
||||
|
||||
COPY --chmod=0755 docker/unoserver/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
COPY --chmod=0755 docker/unoserver/healthcheck.sh /usr/local/bin/healthcheck.sh
|
||||
|
||||
USER unoserver
|
||||
WORKDIR /home/unoserver
|
||||
|
||||
EXPOSE 2003
|
||||
|
||||
HEALTHCHECK --interval=15s --timeout=5s --retries=4 --start-period=45s \
|
||||
CMD /usr/local/bin/healthcheck.sh
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
LABEL org.opencontainers.image.title="Stirling-PDF Unoserver" \
|
||||
org.opencontainers.image.description="Pinned unoserver build for Stirling-PDF remote UNO mode" \
|
||||
org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.vendor="Stirling-Tools"
|
||||
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
PORT="${UNOSERVER_PORT:-2003}"
|
||||
UNO_PORT="${UNOSERVER_UNO_PORT:-2002}"
|
||||
INTERFACE="${UNOSERVER_INTERFACE:-0.0.0.0}"
|
||||
CONVERSION_TIMEOUT="${UNOSERVER_CONVERSION_TIMEOUT:-1800}"
|
||||
RECYCLE_INTERVAL_SECONDS="${UNOSERVER_RECYCLE_INTERVAL_SECONDS:-0}"
|
||||
RECYCLE_INTERVAL_FLOOR=60
|
||||
PROFILE_DIR="${UNOSERVER_PROFILE_DIR:-/var/lib/unoserver/profile}"
|
||||
|
||||
log() { printf '%s %s\n' "[unoserver-entrypoint]" "$*" >&2; }
|
||||
|
||||
case "$PORT" in ''|*[!0-9]*) log "Invalid UNOSERVER_PORT='$PORT'"; exit 64 ;; esac
|
||||
case "$UNO_PORT" in ''|*[!0-9]*) log "Invalid UNOSERVER_UNO_PORT='$UNO_PORT'"; exit 64 ;; esac
|
||||
case "$CONVERSION_TIMEOUT" in ''|*[!0-9]*) log "Invalid UNOSERVER_CONVERSION_TIMEOUT='$CONVERSION_TIMEOUT'"; exit 64 ;; esac
|
||||
case "$RECYCLE_INTERVAL_SECONDS" in ''|*[!0-9]*) log "Invalid UNOSERVER_RECYCLE_INTERVAL_SECONDS='$RECYCLE_INTERVAL_SECONDS'"; exit 64 ;; esac
|
||||
|
||||
mkdir -p "$PROFILE_DIR"
|
||||
|
||||
start_xvfb() {
|
||||
if command -v Xvfb >/dev/null 2>&1 && [ -z "${DISPLAY:-}" ]; then
|
||||
Xvfb :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset >/dev/null 2>&1 &
|
||||
XVFB_PID=$!
|
||||
export DISPLAY=:99
|
||||
sleep 1
|
||||
log "Xvfb started (pid $XVFB_PID, DISPLAY=$DISPLAY)"
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
trap '' TERM INT EXIT
|
||||
if [ -n "${UNOSERVER_PID:-}" ] && kill -0 "$UNOSERVER_PID" 2>/dev/null; then
|
||||
log "Stopping unoserver (pid $UNOSERVER_PID)"
|
||||
pkill -TERM -P "$UNOSERVER_PID" 2>/dev/null || true
|
||||
kill -TERM "$UNOSERVER_PID" 2>/dev/null || true
|
||||
wait "$UNOSERVER_PID" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "${XVFB_PID:-}" ] && kill -0 "$XVFB_PID" 2>/dev/null; then
|
||||
kill -TERM "$XVFB_PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
trap cleanup TERM INT EXIT
|
||||
|
||||
start_unoserver() {
|
||||
log "Starting unoserver on ${INTERFACE}:${PORT} (uno-port ${UNO_PORT}, timeout ${CONVERSION_TIMEOUT}s, profile ${PROFILE_DIR})"
|
||||
# Pass --user-installation as a plain path; unoserver 3.6 wraps it itself
|
||||
# and crashes if pre-wrapped as a file:// URI.
|
||||
unoserver \
|
||||
--interface "$INTERFACE" \
|
||||
--port "$PORT" \
|
||||
--uno-port "$UNO_PORT" \
|
||||
--user-installation "${PROFILE_DIR}" \
|
||||
--conversion-timeout "$CONVERSION_TIMEOUT" \
|
||||
2> >(grep --line-buffered -v "POST /RPC2" >&2) \
|
||||
&
|
||||
UNOSERVER_PID=$!
|
||||
}
|
||||
|
||||
# Wall-clock recycle to bound LibreOffice memory growth. wait -n is unreliable
|
||||
# here because the unoserver job is wrapped in a process substitution.
|
||||
recycle_supervisor() {
|
||||
if [ "$RECYCLE_INTERVAL_SECONDS" -le 0 ]; then
|
||||
log "Recycle disabled (UNOSERVER_RECYCLE_INTERVAL_SECONDS=0)"
|
||||
wait "$UNOSERVER_PID"
|
||||
return $?
|
||||
fi
|
||||
local interval="$RECYCLE_INTERVAL_SECONDS"
|
||||
if [ "$interval" -lt "$RECYCLE_INTERVAL_FLOOR" ]; then
|
||||
log "Clamping recycle interval ${interval}s up to floor ${RECYCLE_INTERVAL_FLOOR}s"
|
||||
interval="$RECYCLE_INTERVAL_FLOOR"
|
||||
fi
|
||||
log "Recycle enabled: restart every ${interval}s"
|
||||
while true; do
|
||||
local elapsed=0
|
||||
while [ "$elapsed" -lt "$interval" ]; do
|
||||
if ! kill -0 "$UNOSERVER_PID" 2>/dev/null; then
|
||||
wait "$UNOSERVER_PID"
|
||||
local rc=$?
|
||||
log "unoserver exited on its own (rc=$rc); not recycling"
|
||||
return "$rc"
|
||||
fi
|
||||
sleep 1
|
||||
elapsed=$((elapsed + 1))
|
||||
done
|
||||
log "Recycling unoserver (pid ${UNOSERVER_PID})"
|
||||
pkill -TERM -P "$UNOSERVER_PID" 2>/dev/null || true
|
||||
kill -TERM "$UNOSERVER_PID" 2>/dev/null || true
|
||||
for _ in 1 2 3 4 5; do
|
||||
kill -0 "$UNOSERVER_PID" 2>/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
pkill -KILL -P "$UNOSERVER_PID" 2>/dev/null || true
|
||||
kill -KILL "$UNOSERVER_PID" 2>/dev/null || true
|
||||
wait "$UNOSERVER_PID" 2>/dev/null || true
|
||||
rm -rf "${PROFILE_DIR:?}"/* 2>/dev/null || true
|
||||
start_unoserver
|
||||
log "unoserver restarted (pid ${UNOSERVER_PID})"
|
||||
done
|
||||
}
|
||||
|
||||
start_xvfb
|
||||
start_unoserver
|
||||
recycle_supervisor
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version='1.0'?>
|
||||
<!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
|
||||
<!-- Subpixel hinting + slight hint + LCD filter for headless LibreOffice. -->
|
||||
<fontconfig>
|
||||
<match target="font">
|
||||
<edit mode="assign" name="rgba">
|
||||
<const>rgb</const>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<edit mode="assign" name="hinting">
|
||||
<bool>true</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<edit mode="assign" name="hintstyle">
|
||||
<const>hintslight</const>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<edit mode="assign" name="antialias">
|
||||
<bool>true</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<edit mode="assign" name="lcdfilter">
|
||||
<const>lcddefault</const>
|
||||
</edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Real RPC ping — a TCP socket can be bound while LibreOffice is wedged.
|
||||
set -eu
|
||||
|
||||
PORT="${UNOSERVER_PORT:-2003}"
|
||||
|
||||
if command -v unoping >/dev/null 2>&1; then
|
||||
unoping --host 127.0.0.1 --port "$PORT" >/dev/null 2>&1
|
||||
exit $?
|
||||
fi
|
||||
|
||||
exec timeout 2 bash -c "cat < /dev/null > /dev/tcp/127.0.0.1/${PORT}"
|
||||
Reference in New Issue
Block a user