Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@
"pamaron.pytest-runner"
]
}
}
}
},
"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
],
"postStartCommand": "sudo chown $USER:$USER /var/run/docker.sock"
}
6 changes: 4 additions & 2 deletions .github/actions/docker-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ name: Setup Docker workflow
description: Shared steps for Docker builds and tests.
inputs:
registry-token:
description: Token used to authenticate against the container registry.
required: true
description: Token used to authenticate against the container registry. Optional — if not provided the login step will be skipped.
required: false
default: ''
outputs:
image_base:
description: Base image name used for GHCR publishes.
Expand Down Expand Up @@ -32,6 +33,7 @@ runs:
images: ${{ steps.image_base.outputs.IMAGE_BASE }}

- name: Login to GitHub Container Registry
if: ${{ inputs.registry-token != '' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
Expand Down
31 changes: 27 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
pull_request:

jobs:
test:
unit-test:
runs-on: ubuntu-latest

steps:
Expand All @@ -15,8 +15,6 @@ jobs:
- name: Setup Docker workflow
id: docker_setup
uses: ./.github/actions/docker-setup
with:
registry-token: ${{ secrets.GITHUB_TOKEN }}

- name: Build Docker test image
uses: docker/build-push-action@v6
Expand Down Expand Up @@ -57,4 +55,29 @@ jobs:
name: test-results
path: |
coverage
code-coverage-results.md
code-coverage-results.md

# Note: harp tests need to run on a host where docker is available
harp-integration-test:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Docker workflow
id: docker_setup
uses: ./.github/actions/docker-setup

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dev dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt

- name: Run HaRP Docker Integration Tests
run: make harp-integrationtest
11 changes: 11 additions & 0 deletions .vscode/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"servers": {
"github/github-mcp-server": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"gallery": "https://api.mcp.github.com",
"version": "0.13.0"
}
},
"inputs": []
}
48 changes: 39 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,35 +1,65 @@
FROM python:3.12-alpine3.21 AS app
FROM python:3.12-alpine AS app

ARG USER=serviceuser

ENV USER=$USER
ENV HOME=/home/$USER
ENV GOSU_VERSION=1.19

RUN apk update && \
apk add --no-cache sudo ocrmypdf $(apk search tesseract-ocr-data- | sed 's/-[0-9].*//') && \
adduser -D $USER
apk add --no-cache ocrmypdf $(apk search tesseract-ocr-data- | sed 's/-[0-9].*//') curl bash frp ca-certificates && \
adduser -D $USER && \
touch /frpc.toml && \
mkdir -p /certs && \
chown -R $USER:$USER /frpc.toml /certs && \
chmod 600 /frpc.toml

USER $USER
# 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 --chown=$USER:$USER requirements.txt requirements.txt
COPY --chown=$USER:$USER main.py .
COPY --chown=$USER:$USER workflow_ocr_backend/ ./workflow_ocr_backend
COPY --chown=$USER:$USER start.sh /start.sh
RUN chmod +x /start.sh && \
chown -R $USER:$USER /app && \
pip install -r requirements.txt

RUN pip install -r requirements.txt

ENTRYPOINT ["python3", "-u", "main.py"]
ENTRYPOINT ["/bin/sh", "-c", "exec gosu \"$USER\" /start.sh python3 -u main.py"]

FROM app AS devcontainer

COPY --chown=$USER:$USER requirements-dev.txt requirements-dev.txt

# Install dev dependencies and set up sudo
USER root
RUN apk add --no-cache git curl make gnupg && \
RUN apk add --no-cache sudo git docker-cli make gnupg && \
echo "$USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USER && \
chmod 0440 /etc/sudoers.d/$USER
USER $USER
RUN pip install -r requirements-dev.txt
RUN pip install -r requirements-dev.txt

FROM devcontainer AS test

Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ deps:

.PHONY: test
test:
python -m pytest --cov-report html:coverage --cov-report xml:coverage/coverage.xml --cov=workflow_ocr_backend test
python -m pytest --cov-report html:coverage --cov-report xml:coverage/coverage.xml --cov=workflow_ocr_backend -m "not harp_integration" test

.PHONY: harp-integrationtest
harp-integrationtest:
python -m pytest -m "harp_integration" test

.PHONY: build
build:
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ It's written in Python and provides a simple REST API for [ocrmypdf](https://ocr
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [`docker-compose` Example](#docker-compose-example)
- [HaRP Support (Nextcloud 32+)](#harp-support-nextcloud-32)

## Prerequisites

Expand All @@ -17,7 +18,9 @@ It will take care of all the heavy lifting like installation, orchestration, con

1. Install [`docker`](https://docs.docker.com/engine/install/ubuntu/) on the host where the app should be installed.
2. Install the [`AppApi`](https://docs.nextcloud.com/server/latest/admin_manual/exapps_management/AppAPIAndExternalApps.html#installing-appapi) app. It will take care of the installation and orchestration of the backend as Docker Container.
3. Setup a [Deploy Daemon](https://docs.nextcloud.com/server/latest/admin_manual/exapps_management/AppAPIAndExternalApps.html#setup-deploy-daemon). It's recommended to use the [Docker Socket Proxy](https://github.com/nextcloud/docker-socket-proxy#readme) to communicate with the docker daemon.
3. Setup a [Deploy Daemon](https://docs.nextcloud.com/server/latest/admin_manual/exapps_management/AppAPIAndExternalApps.html#setup-deploy-daemon):
- **For Nextcloud 32+**: It's recommended to use [HaRP (AppAPI HaProxy Reversed Proxy)](https://github.com/nextcloud/HaRP) for better performance and simplified deployment.
- **For older versions**: Use the [Docker Socket Proxy](https://github.com/nextcloud/docker-socket-proxy#readme) to communicate with the docker daemon.

## Installation

Expand Down Expand Up @@ -164,3 +167,11 @@ nc_py_api._exceptions.NextcloudException: [400] Bad Request <request: PUT /ocs/v

> :warning: Make sure to create the docker network `nextcloud` before starting the stack. If you don't declare the network as
> `external`, `docker-compose` will create the network with some [project/directory prefix](https://docs.docker.com/compose/how-tos/networking/), which will cause the Deploy Daemon to fail because it doesn't find the network.

## HaRP Support (Nextcloud 32+)

Since Nextcloud 32, [HaRP (AppAPI HaProxy Reversed Proxy)](https://github.com/nextcloud/HaRP) is the recommended deployment method for ExApps, replacing Docker Socket Proxy. This app now supports HaRP out of the box.

HaRP simplifies deployment and improves performance by enabling direct communication between clients and ExApps. The implementation is fully backward compatible with Docker Socket Proxy deployments.

For installation and migration instructions, see the [HaRP documentation](https://github.com/nextcloud/HaRP#readme).
5 changes: 5 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[pytest]
log_cli = true
log_cli_level = INFO
markers =
harp_integration: mark test as full HaRP integration. Spins up HaRP container and manages ExApp lifecycle
65 changes: 65 additions & 0 deletions start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: AGPL-3.0-or-later

set -e

# Only create a config file if HP_SHARED_KEY is set.
if [ -n "$HP_SHARED_KEY" ]; then
echo "HP_SHARED_KEY is set, creating /frpc.toml configuration file..."
if [ -d "/certs/frp" ]; then
echo "Found /certs/frp directory. Creating configuration with TLS certificates."
cat <<EOF > /frpc.toml
serverAddr = "$HP_FRP_ADDRESS"
serverPort = $HP_FRP_PORT
loginFailExit = false

transport.tls.enable = true
transport.tls.certFile = "/certs/frp/client.crt"
transport.tls.keyFile = "/certs/frp/client.key"
transport.tls.trustedCaFile = "/certs/frp/ca.crt"
transport.tls.serverName = "harp.nc"

metadatas.token = "$HP_SHARED_KEY"

[[proxies]]
remotePort = $APP_PORT
type = "tcp"
name = "$APP_ID"
[proxies.plugin]
type = "unix_domain_socket"
unixPath = "/tmp/exapp.sock"
EOF
else
echo "Directory /certs/frp not found. Creating configuration without TLS certificates."
cat <<EOF > /frpc.toml
serverAddr = "$HP_FRP_ADDRESS"
serverPort = $HP_FRP_PORT
loginFailExit = false

transport.tls.enable = false

metadatas.token = "$HP_SHARED_KEY"

[[proxies]]
remotePort = $APP_PORT
type = "tcp"
name = "$APP_ID"
[proxies.plugin]
type = "unix_domain_socket"
unixPath = "/tmp/exapp.sock"
EOF
fi
else
echo "HP_SHARED_KEY is not set. Skipping FRP configuration."
fi

# If we have a configuration file and the shared key is present, start the FRP client
if [ -f /frpc.toml ] && [ -n "$HP_SHARED_KEY" ]; then
echo "Starting frpc in the background..."
frpc -c /frpc.toml &
fi

# Start the main application (launch cmd for ExApp is an argument for this script)
echo "Starting application: $@"
exec "$@"
Loading