Merge pull request #13960 from nextcloud/feat/add-exapp-harp-docs

feat(exapp_development): Add documentation for ExApp HaRP
This commit is contained in:
Oleksandr Piskun
2025-12-30 17:40:06 +02:00
committed by GitHub
2 changed files with 254 additions and 0 deletions

View File

@@ -0,0 +1,253 @@
Adapting ExApps to HaRP
=======================
.. mermaid::
graph LR
Client[Client] -->|connects| NC[Nextcloud Proxy]
NC -->|connects| HaRP[HaRP - FRP proxy]
HaRP -->|forwards| ExApp[ExApp container]
ExApp -->|runs FRP client| HaRP
AppAPI[Nextcloud AppAPI] -->|manage certs| ExApp
Summary
-------
HaRP is a reverse proxy system designed to simplify the deployment workflow
for Nextcloud 32s AppAPI.
It enables direct communication between clients and ExApps, bypassing
the Nextcloud instance to improve performance and reduce the complexity
traditionally associated with `DockerSocketProxy` setups.
HaRP provides an `FRP-based <https://github.com/fatedier/frp>`_
transport for ExApps and recommends copying
`start.sh <https://github.com/nextcloud/HaRP/blob/main/exapps_dev/start.sh>`_
into your ExApp image and using it as the container entrypoint.
The script installs or starts the FRP client and executes your app process.
.. warning::
We strongly recommend starting support for HaRP in ExApps from the start
of Nextcloud 32, as the old `DSP <https://github.com/nextcloud/docker-socket-proxy>`_
way will be deprecated and marked for removal in Nextcloud 35.
Adding HaRP support is fully compatible with the existing DSP system,
so you wont need to maintain two separate release types of your ExApp.
Key integration considerations
------------------------------
- **Connecting to HaRP with FRPC**: Your ExApp does not need to expose any ports to the host
or be reachable from the Nextcloud server. The FRP client (`FRPC`) inside your ExApp
container will create an outbound connection to HaRP, which will proxy
requests from clients to your ExApp.
- **File permissions**: AppAPI may copy certificate files into the container and
execute commands inside it. If your container runs the main process as a
non-root service user, AppAPI's file writes or execs may fail unless the
receiving paths are writable/readable by that user.
- **Certs and FRP config**: HaRP expects FRP cert files to be accessible under
`/certs/frp` (client.crt, client.key, ca.crt). The FRP client configuration
path used by many example `start.sh` scripts is `/frpc.toml`.
- **Root-only commands**: Some setup steps (for example updating CA bundles with
`update-ca-certificates`) require root; AppAPI may need to run those using
`docker exec ...` when setting up containers.
Steps needed to adapt an ExApp
--------------------------------------
1. Copy the `start.sh <https://github.com/nextcloud/HaRP/blob/main/exapps_dev/start.sh>`_
script from the exapps_dev folder of the HaRP repository into your Docker image
(e.g., using a `COPY` instruction).
2. In your ExApp's Dockerfile, set the `ENTRYPOINT` to execute `start.sh` followed by
the **command and arguments required to launch** your actual application. The `start.sh`
script will launch the FRP client if needed and then use `exec` to
run the command you provide as arguments.
3. Ensure the `curl` command-line utility is installed in your ExApp's Docker image,
as it's needed by the following script to download the FRP client.
4. Add the following lines to your Dockerfile to automatically include the FRP
client binaries in your Docker image:
.. code-block:: dockerfile
# Download and install FRP client
RUN set -ex; \
ARCH=$(uname -m); \
if [ "$ARCH" = "aarch64" ]; then \
FRP_URL="https://raw.githubusercontent.com/nextcloud/HaRP/main/exapps_dev/frp_0.61.1_linux_arm64.tar.gz"; \
else \
FRP_URL="https://raw.githubusercontent.com/nextcloud/HaRP/main/exapps_dev/frp_0.61.1_linux_amd64.tar.gz"; \
fi; \
echo "Downloading FRP client from $FRP_URL"; \
curl -L "$FRP_URL" -o /tmp/frp.tar.gz; \
tar -C /tmp -xzf /tmp/frp.tar.gz; \
mv /tmp/frp_0.61.1_linux_* /tmp/frp; \
cp /tmp/frp/frpc /usr/local/bin/frpc; \
chmod +x /usr/local/bin/frpc; \
rm -rf /tmp/frp /tmp/frp.tar.gz
.. note::
For Alpine 3.21 Linux you can just install FRP from repo using apk add frp command.
Running your ExApp with a non-root user
----------------------------------------
.. note::
In `Docker Build best practices <https://docs.docker.com/build/building/best-practices/#user>`_,
it is recommended to run application containers as non-root users for security reasons
whenever possible.
To run the main process as a non-root user while remaining compatible with HaRP and AppAPI,
ensure the following:
1. Keep image default user as `root`, drop to less privileged service user at runtime.
- Make it easy for AppAPI to perform privileged operations (copying files,
setting permissions, running `update-ca-certificates`) by leaving the
container default user as `root` in the image.
- Drop privileges for the main process in the `ENTRYPOINT` using `gosu`
or `su-exec` so the runtime process runs as a non-root service user.
Example snippet (Dockerfile):
.. code-block:: dockerfile
FROM python:3.12-alpine AS app
ARG USER=serviceuser
ENV USER=$USER
ENV HOME=/home/$USER
ENV GOSU_VERSION=1.19
# ... other Dockerfile instructions ..
# Install GOSU
RUN set -eux; \
\
apk add --no-cache --virtual .gosu-deps \
ca-certificates \
dpkg \
gnupg \
; \
\
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
\
export GNUPGHOME="$(mktemp -d)"; \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
gpgconf --kill all; \
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
\
apk del --no-network .gosu-deps; \
\
chmod +x /usr/local/bin/gosu
# Use gosu in combination with start.sh
ENTRYPOINT ["/bin/sh", "-c", "exec gosu \"$USER\" /start.sh python3 -u main.py"]
.. note::
See the `gosu documentation <https://github.com/tianon/gosu/blob/master/INSTALL.md>`_
for more details.
2. Ensure FRP config and cert paths are prepared for the service user
- **Create `/frpc.toml`** or the directory that will contain it at image build
time and set ownership to the service user so at runtime `start.sh` can
write to it without requiring root.
- **Create directory `/certs/frp`** and make it readable by the service user.
AppAPI will copy cert files into that folder by using a `docker cp ...` command
with the default container user (which is still `root`). By setting the directory
owner to the service user, we will ensure the service user can read the certs at runtime.
Use some similar commands in your Dockerfile:
.. code-block:: dockerfile
RUN touch /frpc.toml && \
mkdir -p /certs/frp && \
chown $USER:$USER /frpc.toml && \
chown -R $USER:$USER /certs/frp && \
chmod 600 /frpc.toml
**Putting it all together:**
.. code-block:: dockerfile
FROM python:3.12-alpine AS app
ARG USER=serviceuser
ENV USER=$USER
ENV HOME=/home/$USER
ENV GOSU_VERSION=1.19
# Install dependencies and create service user. You might want to
# add additional packages depending on your app requirements.
# Make sure curl and FRP are installed.
RUN apk update && \
apk add --no-cache curl frp ca-certificates && \
adduser -D $USER && \
touch /frpc.toml && \
mkdir -p /certs/frp && \
chown $USER:$USER /frpc.toml && \
chown -R $USER:$USER /certs/frp && \
chmod 600 /frpc.toml
# Install GOSU
RUN set -eux; \
\
apk add --no-cache --virtual .gosu-deps \
ca-certificates \
dpkg \
gnupg \
; \
\
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
\
export GNUPGHOME="$(mktemp -d)"; \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
gpgconf --kill all; \
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
\
apk del --no-network .gosu-deps; \
\
chmod +x /usr/local/bin/gosu
WORKDIR /app
# Copy your app code
COPY --chown=$USER:$USER <files> .
# Copy the start.sh script and make it executable
COPY --chown=$USER:$USER start.sh /start.sh
RUN chmod +x /start.sh && \
chown -R $USER:$USER /app && \
pip install -r requirements.txt
# Run the start.sh as entrypoint with non-root user and point it to your app
ENTRYPOINT ["/bin/sh", "-c", "exec gosu \"$USER\" /start.sh python3 -u main.py"]
Integration test example
-------------------------
An example test suite used to validate HaRP support for an ExApp is available
in the `workflow_ocr_backend` repository (example commit that added HaRP
support and tests):
- https://github.com/R0Wi-DEV/workflow_ocr_backend/blob/f5ae6efb6e4a3307328a188898968abf000511ab/test/test_harp_integration.py
This test demonstrates automated verification of FRP connection and runtime
behaviour; it can be used as a reference when adding CI checks for HaRP
compatibility.

View File

@@ -13,4 +13,5 @@ grouped from other parts of documentation.
ExAppDevelopmentSteps
ExAppOverview
ExAppLifecycle
ExAppHarpIntegration