Skip to content

Commit 155daf5

Browse files
committed
feat(exapp_development): Add documentation for ExApp HaRP
Signed-off-by: Robin Windey <[email protected]>
1 parent 94a5a1e commit 155daf5

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
Adapting ExApps to HaRP
2+
======================
3+
4+
.. mermaid::
5+
6+
graph LR
7+
Client[Client] -->|connects| NC[Nextcloud Proxy]
8+
NC -->|connects| HaRP[HaRP - FRP proxy]
9+
HaRP -->|forwards| ExApp[ExApp container]
10+
ExApp -->|runs FRP client| HaRP
11+
AppAPI[Nextcloud AppAPI] -->|manage certs| ExApp
12+
13+
Summary
14+
-------
15+
16+
HaRP is a reverse proxy system designed to simplify the deployment workflow
17+
for Nextcloud 32’s AppAPI.
18+
19+
It enables direct communication between clients and ExApps, bypassing
20+
the Nextcloud instance to improve performance and reduce the complexity
21+
traditionally associated with `DockerSocketProxy` setups.
22+
23+
HaRP provides an `FRP-based <https://github.com/fatedier/frp>`_
24+
transport for ExApps and recommends copying
25+
`start.sh <https://github.com/nextcloud/HaRP/blob/main/exapps_dev/start.sh>`_
26+
into your ExApp image and using it as the container entrypoint.
27+
The script installs or starts the FRP client and executes your app process.
28+
29+
.. warning::
30+
31+
We strongly recommend starting support for HaRP in ExApps from the start
32+
of Nextcloud 32, as the old `DSP <https://github.com/nextcloud/docker-socket-proxy>`_
33+
way will be deprecated and marked for removal in Nextcloud 35.
34+
35+
Adding HaRP support is fully compatible with the existing DSP system,
36+
so you won’t need to maintain two separate release types of your ExApp.
37+
38+
Key integration considerations
39+
------------------------------
40+
41+
- **Connecting to HaRP with FRPC**: Your ExApp does not need to expose any ports to the host
42+
or be reachable from the Nextcloud server. The FRP client (`FRPC`) inside your ExApp
43+
container will create an outbound connection to HaRP, which will proxy
44+
requests from clients to your ExApp.
45+
- **File permissions**: AppAPI may copy certificate files into the container and
46+
execute commands inside it. If your container runs the main process as a
47+
non-root service user, AppAPI's file writes or execs may fail unless the
48+
receiving paths are writable/readable by that user.
49+
- **Certs and FRP config**: HaRP expects FRP cert files to be accessible under
50+
`/certs/frp` (client.crt, client.key, ca.crt). The FRP client configuration
51+
path used by many example `start.sh` scripts is `/frpc.toml`.
52+
- **Root-only commands**: Some setup steps (for example updating CA bundles with
53+
`update-ca-certificates`) require root; AppAPI may need to run those using
54+
`docker exec ...` when setting up containers.
55+
56+
Steps needed to adapt an ExApp
57+
--------------------------------------
58+
59+
1. Copy the `start.sh <https://github.com/nextcloud/HaRP/blob/main/exapps_dev/start.sh>`_
60+
script from the exapps_dev folder of the HaRP repository into your Docker image
61+
(e.g., using a `COPY` instruction).
62+
63+
2. In your ExApp's Dockerfile, set the `ENTRYPOINT` to execute `start.sh` followed by
64+
the **command and arguments required to launch** your actual application. The `start.sh`
65+
script will launch the FRP client if needed and then use `exec` to
66+
run the command you provide as arguments.
67+
68+
3. Ensure the `curl` command-line utility is installed in your ExApp's Docker image,
69+
as it's needed by the following script to download the FRP client.
70+
71+
4. Add the following lines to your Dockerfile to automatically include the FRP
72+
client binaries in your Docker image:
73+
74+
.. code-block:: dockerfile
75+
76+
# Download and install FRP client
77+
RUN set -ex; \
78+
ARCH=$(uname -m); \
79+
if [ "$ARCH" = "aarch64" ]; then \
80+
FRP_URL="https://raw.githubusercontent.com/nextcloud/HaRP/main/exapps_dev/frp_0.61.1_linux_arm64.tar.gz"; \
81+
else \
82+
FRP_URL="https://raw.githubusercontent.com/nextcloud/HaRP/main/exapps_dev/frp_0.61.1_linux_amd64.tar.gz"; \
83+
fi; \
84+
echo "Downloading FRP client from $FRP_URL"; \
85+
curl -L "$FRP_URL" -o /tmp/frp.tar.gz; \
86+
tar -C /tmp -xzf /tmp/frp.tar.gz; \
87+
mv /tmp/frp_0.61.1_linux_* /tmp/frp; \
88+
cp /tmp/frp/frpc /usr/local/bin/frpc; \
89+
chmod +x /usr/local/bin/frpc; \
90+
rm -rf /tmp/frp /tmp/frp.tar.gz
91+
92+
.. note::
93+
94+
For Alpine 3.21 Linux you can just install FRP from repo using apk add frp command.
95+
96+
Running your ExApp with a non-root user
97+
--------------------------------------
98+
99+
.. note::
100+
101+
In `Docker Build best practices <https://docs.docker.com/build/building/best-practices/#user>`_,
102+
it is recommended to run application containers as non-root users for security reasons
103+
whenever possible.
104+
105+
To run the main process as a non-root user while remaining compatible with HaRP and AppAPI,
106+
ensure the following:
107+
108+
1. Keep image default user as `root`, drop to less privileged service user at runtime.
109+
110+
- Make it easy for AppAPI to perform privileged operations (copying files,
111+
setting permissions, running `update-ca-certificates`) by leaving the
112+
container default user as `root` in the image.
113+
- Drop privileges for the main process in the `ENTRYPOINT` using `gosu`
114+
or `su-exec` so the runtime process runs as a non-root service user.
115+
116+
Example snippet (Dockerfile):
117+
118+
.. code-block:: dockerfile
119+
120+
FROM python:3.12-alpine AS app
121+
122+
ARG USER=serviceuser
123+
124+
ENV USER=$USER
125+
ENV HOME=/home/$USER
126+
ENV GOSU_VERSION=1.19
127+
128+
# ... other Dockerfile instructions ..
129+
130+
# Install GOSU
131+
RUN set -eux; \
132+
\
133+
apk add --no-cache --virtual .gosu-deps \
134+
ca-certificates \
135+
dpkg \
136+
gnupg \
137+
; \
138+
\
139+
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
140+
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
141+
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
142+
\
143+
export GNUPGHOME="$(mktemp -d)"; \
144+
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
145+
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
146+
gpgconf --kill all; \
147+
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
148+
\
149+
apk del --no-network .gosu-deps; \
150+
\
151+
chmod +x /usr/local/bin/gosu
152+
153+
# Use gosu in combination with start.sh
154+
ENTRYPOINT ["/bin/sh", "-c", "exec gosu \"$USER\" /start.sh python3 -u main.py"]
155+
156+
.. note::
157+
158+
See the `gosu documentation <https://github.com/tianon/gosu/blob/master/INSTALL.md>`_
159+
for more details.
160+
161+
2. Ensure FRP config and cert paths are prepared for the service user
162+
163+
- **Create `/frpc.toml`** or the directory that will contain it at image build
164+
time and set ownership to the service user so at runtime `start.sh` can
165+
write to it without requiring root.
166+
- **Create directory `/certs/frp`** and make it readable by the service user.
167+
AppAPI will copy cert files into that folder by using a `docker cp ...` command
168+
with the default container user (which is still `root`). By setting the directory
169+
owner to the service user, we will ensure the service user can read the certs at runtime.
170+
171+
Use some similar commands in your Dockerfile:
172+
173+
.. code-block:: dockerfile
174+
175+
RUN touch /frpc.toml && \
176+
mkdir -p /certs/frp && \
177+
chown $USER:$USER /frpc.toml && \
178+
chown -R $USER:$USER /certs/frp && \
179+
chmod 600 /frpc.toml
180+
181+
**Putting it all together:**
182+
183+
.. code-block:: dockerfile
184+
185+
FROM python:3.12-alpine AS app
186+
187+
ARG USER=serviceuser
188+
189+
ENV USER=$USER
190+
ENV HOME=/home/$USER
191+
ENV GOSU_VERSION=1.19
192+
193+
# Install dependencies and create service user. You might want to
194+
# add additional packages depending on your app requirements.
195+
# Make sure curl and FRP are installed.
196+
RUN apk update && \
197+
apk add --no-cache curl frp ca-certificates && \
198+
adduser -D $USER && \
199+
touch /frpc.toml && \
200+
mkdir -p /certs/frp && \
201+
chown $USER:$USER /frpc.toml && \
202+
chown -R $USER:$USER /certs/frp && \
203+
chmod 600 /frpc.toml
204+
205+
# Install GOSU
206+
RUN set -eux; \
207+
\
208+
apk add --no-cache --virtual .gosu-deps \
209+
ca-certificates \
210+
dpkg \
211+
gnupg \
212+
; \
213+
\
214+
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
215+
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
216+
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
217+
\
218+
export GNUPGHOME="$(mktemp -d)"; \
219+
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
220+
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
221+
gpgconf --kill all; \
222+
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
223+
\
224+
apk del --no-network .gosu-deps; \
225+
\
226+
chmod +x /usr/local/bin/gosu
227+
228+
WORKDIR /app
229+
230+
# Copy your app code
231+
COPY --chown=$USER:$USER <files> .
232+
233+
# Copy the start.sh script and make it executable
234+
COPY --chown=$USER:$USER start.sh /start.sh
235+
RUN chmod +x /start.sh && \
236+
chown -R $USER:$USER /app && \
237+
pip install -r requirements.txt
238+
239+
# Run the start.sh as entrypoint with non-root user and point it to your app
240+
ENTRYPOINT ["/bin/sh", "-c", "exec gosu \"$USER\" /start.sh python3 -u main.py"]
241+
242+
Integration test example
243+
---------------------
244+
245+
An example test suite used to validate HaRP support for an ExApp is available
246+
in the `workflow_ocr_backend` repository (example commit that added HaRP
247+
support and tests):
248+
249+
- https://github.com/R0Wi-DEV/workflow_ocr_backend/blob/f5ae6efb6e4a3307328a188898968abf000511ab/test/test_harp_integration.py
250+
251+
This test demonstrates automated verification of FRP connection and runtime
252+
behaviour; it can be used as a reference when adding CI checks for HaRP
253+
compatibility.

developer_manual/exapp_development/development_overview/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ grouped from other parts of documentation.
1313
ExAppDevelopmentSteps
1414
ExAppOverview
1515
ExAppLifecycle
16+
ExAppHarpIntegration
1617

0 commit comments

Comments
 (0)