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 32’s 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 `_ transport for ExApps and recommends copying `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 `_ way will be deprecated and marked for removal in Nextcloud 35. Adding HaRP support is fully compatible with the existing DSP system, so you won’t 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 `_ 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 `_, 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 `_ 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 . # 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.