Self-contained deployment for Content Lake App -- ingests content from Alfresco and Nuxeo into hxpr for hybrid semantic search and RAG.
Part of the Content Lake ecosystem -- a PoC for ingesting Alfresco and Nuxeo content into the ai-ready-index engine for hybrid semantic search and RAG.
| Repo | Role |
|---|---|
| content-lake-app | Java ingestion pipeline and RAG service |
| content-lake-app-deployment | Docker Compose stack that wires everything together (this repo) |
| alfresco-content-lake-ui | ACA/ADW extension: semantic search + RAG chat sidebar |
| content-lake-app-ui | Standalone demo UI (Alfresco + Nuxeo dual auth) |
| nuxeo-deployment | Local Nuxeo + PostgreSQL stack (required for Nuxeo profiles) |
git clone https://github.com/aborroy/content-lake-app-deployment.git
cd content-lake-app-deployment
./setup.sh # checks prerequisites, pulls AI models, prompts for credentials, starts the stackThe setup script handles everything for a first run. For manual control see First Run below.
The stack supports four source profiles. Start with alfresco if you only have Alfresco:
make up-alfresco # Alfresco + HXPR + RAG + ACA UI (~14 services)
make up-nuxeo # Nuxeo + HXPR + RAG (~9 services, 2 from ../nuxeo-deployment)
make up-full # Alfresco + Nuxeo + HXPR + RAG (~18 services)
make up-demo # full + standalone demo UI at / (~19 services)A mounted directory is one of those plugins since content-lake-app#148, in place of the retired
filesystem profile. Build plugins/filesystem-connector into connectors/, mount the
content at CONNECTOR_HOST_PATH (default ./filesystem-data), and set CONNECTOR_SOURCE_TYPE=filesystem.
The FILESYSTEM_* setting names are unchanged, so an existing configuration carries over; the sync API is
guarded by CONNECTOR_SYNC_USERNAME and CONNECTOR_SYNC_PASSWORD rather than the retired
FILESYSTEM_SYNC_* pair, because a filesystem has no user directory to authenticate callers against and
neither has a default.
The plugin-batch-ingester is an opt-in service in its own connector profile, and it is the one that
ingests through a connector plugin. A jar in connectors/ is discovered by every ingester,
but the Alfresco and Nuxeo ingesters each drive a client they were compiled against, so for them a mounted
connector is only listed. This service takes its client, scope rules and optionally
its extractor from the jar, which means a new source needs no Maven module, no Dockerfile edit and no
change to compose.content-lake.yaml:
CONNECTOR_SYNC_USERNAME=admin CONNECTOR_SYNC_PASSWORD=admin \
docker compose --profile alfresco --profile connector up -d --build plugin-batch-ingester
curl -u admin:admin http://localhost:9096/api/connectors
curl -u admin:admin -X POST http://localhost:9096/api/sync/configuredIts source is that jar, so with connectors/ empty it fails to start rather than idling. Set
CONNECTOR_SOURCE_TYPE when several jars are mounted, and CONNECTOR_ROOTS when the connector does not
name its own root container. Its sync API is guarded by one configured account, from
CONNECTOR_SYNC_USERNAME and CONNECTOR_SYNC_PASSWORD, with no defaults.
connectors/README.md has the rest, including how a connector's own settings are
passed in.
sharepoint-mock is a second opt-in profile, layered on top of connector. It runs mock-graph on
:8099, a stand-in for Microsoft Graph serving the SharePoint connector's own test fixtures, so that
connector can be run and demonstrated with no Microsoft 365 tenant. Test tooling rather than a product
service, and it is absent from all four base profiles, which make verify-profiles asserts.
CONNECTOR_SYNC_USERNAME=admin CONNECTOR_SYNC_PASSWORD=admin \
CONNECTOR_SOURCE_TYPE=sharepoint SHAREPOINT_DRIVE_IDS='b!mock-drive-id' \
SHAREPOINT_CLIENT_ID=mock SHAREPOINT_AUTH_MODE=static-token SHAREPOINT_ACCESS_TOKEN=mock-token \
SHAREPOINT_GRAPH_BASE_URL=http://mock-graph:8099/v1.0 SHAREPOINT_RESOURCE_UNITS_PER_MINUTE=0 \
docker compose --profile alfresco --profile connector --profile sharepoint-mock up -dWhat it cannot stand in for is Entra ID: msal4j refuses an authority that is not https, so a mock run
uses static-token and proves nothing about app-only token acquisition. It also cannot say whether a real
tenant honours the Prefer headers, which is what SHAREPOINT_PERMISSIONS_MODE=hierarchical depends on.
Both are covered by the connector's own unit tests against the real library, not here.
For any profile that includes Nuxeo (nuxeo, full, demo), clone nuxeo-deployment as a sibling and start it first:
git clone https://github.com/aborroy/nuxeo-deployment.git ../nuxeo-deployment
(cd ../nuxeo-deployment && docker compose up -d)
make up-fullNo other sibling checkout is required -- all Java services build directly from GitHub via Docker BuildKit.
Important: profiles nuxeo, full, and demo do not start the Nuxeo server itself. The proxy
forwards /nuxeo/* to http://host.docker.internal:8081, so if ../nuxeo-deployment is not
running you will get 502 Bad Gateway on http://localhost/nuxeo/.
The stack is split across five files. compose.yaml is the only entrypoint -- it declares shared
infrastructure (network, named volumes, build secrets) and pulls in the rest via include:.
| File | Contents |
|---|---|
compose.yaml |
Shared network, volumes, secrets + include: list |
compose.alfresco.yaml |
Alfresco: postgres, activemq, alfresco, transform-core-aio, batch-indexer*, control-center* |
compose.hxpr.yaml |
HXPR platform: hxpr-app, mongodb, opensearch, opensearch-dashboards (debug profile) |
compose.content-lake.yaml |
Content Lake services: batch-ingester, live-ingester, rag-service, nuxeo-batch-ingester, nuxeo-live-ingester, plugin-batch-ingester, mock-graph |
compose.ui.yaml |
UI and proxy: content-app, content-lake-app-ui (demo only), proxy |
compose.observability.yaml |
Trace backend for the RAG spans: otel-lgtm (observability profile) |
Always run from the project root using make or docker compose -- the included files are not
designed to be run in isolation.
Every ingester mounts connectors/ read-only at /opt/content-lake/connectors and scans it
at startup, so a source connector can be shipped as a jar instead of as a module of content-lake-app.
That removes the whole ceremony an in-tree source needs: a Maven module, a line in an intermediate POM,
and a COPY line in the service build, which breaks a service's build when forgotten.
cp my-cmis-connector-1.0.0.jar connectors/
docker compose --profile alfresco up -d --force-recreate batch-ingester
curl http://localhost:9090/api/connectors -u admin:admin # what loaded, from which jar, what failedThe directory is empty by default and an empty directory changes nothing. Override the mount with
CONNECTOR_PLUGIN_PATH. A jar that cannot be read, or whose configuration does not satisfy the schema it
publishes, is reported by that endpoint and in the log. The five ingesters that never ingest from a jar
start anyway (CONNECTOR_VALIDATION defaults to warn for them, since a connector they were not going to
use should not stop their own ingestion); plugin-batch-ingester defaults to fail, because for it that
jar is the only source. CONNECTOR_VALIDATION_INGESTERS=fail makes the other five strict as well.
plugin-batch-ingester also mounts one writable directory, published as CONNECTOR_STATE_DIRECTORY
(/var/lib/content-lake/connector), for state a connector cannot recompute such as a change cursor or a
delta token. It is a named volume, so make clean wipes it and make down does not, and a connector
should treat a missing cursor as normal. Nothing reads the variable by itself: a connector declares its
own setting and the operator points it at that path. See
connectors/README.md.
Note that the connector's own settings still have to reach the service. A plugin declares the property names it needs and reads them from the ingester's environment, so they are passed like any other setting.
Three connectors ship with the project, and plugin-batch-ingester already declares the settings of all
three, so each needs only its jar and its values:
| Connector | Source | Notes |
|---|---|---|
plugins/cmis-connector |
any CMIS 1.1 repository | CMIS_* settings |
plugins/sharepoint-connector |
SharePoint Online through Microsoft Graph | SHAREPOINT_* settings. Runs against the mock Graph service in the sharepoint-mock profile with no Microsoft 365 account at all |
plugins/filesystem-connector |
a mounted directory | FILESYSTEM_* settings, in place of the retired filesystem profile |
../content-lake-app/plugins/examples/sample-directory-connector is a hundred-line worked example to read
before writing one, and is not a source anyone should deploy.
SharePoint has one setting worth deciding before a first crawl. SHAREPOINT_PERMISSIONS_MODE defaults to
per-item, measured at 5.60 Graph resource units per document, which bounds a crawl near 200,000 documents
a day; hierarchical measures 1.10 but needs the Sites.FullControl.All application permission, and
refuses to run rather than degrade without it. connectors/README.md has the
figures and where they come from.
| Doc | Contents |
|---|---|
| docs/deployment-alfresco.md | Full stack prerequisites, credentials, first run, Alfresco requirements, configuration reference |
| docs/deployment-nuxeo.md | Nuxeo stack setup, REST API reference, scope/auth config, audit live sync |
| docs/deployment-rag.md | RAG service configuration, REST API, security, conversation memory, observability |
| docs/extraction.md | Text extraction paths, markdown vs plaintext, the transform-extras profile |
| docs/DEPLOY_EC2.md | Step-by-step guide to running the full stack on AWS EC2 |
flowchart LR
Browser["Browser"]
ModelRunner["Docker Model Runner"]
subgraph ACL["content-lake-app"]
Proxy["proxy"]
ContentApp["content-app"]
DemoUi["content-lake-app-ui"]
Batch["alfresco-batch-ingester"]
Live["alfresco-live-ingester"]
NuxeoBatch["nuxeo-batch-ingester"]
NuxeoLive["nuxeo-live-ingester"]
Rag["rag-service"]
end
subgraph ACS["Alfresco"]
Alfresco["alfresco"]
ControlCenter["control-center"]
BatchIndexer["batch-indexer"]
Postgres["postgres"]
ActiveMQ["activemq"]
Transform["transform-core-aio"]
end
subgraph NUXEO["Nuxeo (sibling stack)"]
Nuxeo["nuxeo"]
NuxeoDb["nuxeo-db"]
end
subgraph HXPR["hxpr"]
HxprApp["hxpr-app"]
Mongo["mongodb"]
OpenSearch["opensearch"]
OSD["opensearch-dashboards (debug)"]
end
Browser --> Proxy
Proxy --> ContentApp
Proxy --> DemoUi
Proxy --> Alfresco
Proxy --> ControlCenter
Proxy --> Batch
Proxy --> NuxeoBatch
Proxy --> Nuxeo
Proxy --> Rag
ControlCenter --> Alfresco
Alfresco --> Postgres
Alfresco --> OpenSearch
Alfresco --> ActiveMQ
Alfresco --> Transform
BatchIndexer --> Alfresco
BatchIndexer --> Postgres
BatchIndexer --> Transform
BatchIndexer --> OpenSearch
OSD --> OpenSearch
Nuxeo --> NuxeoDb
Batch --> ActiveMQ
Batch --> Alfresco
Batch --> Transform
Batch --> HxprApp
Batch -.-> ModelRunner
Live --> ActiveMQ
Live --> Alfresco
Live --> Transform
Live --> HxprApp
Live -.-> ModelRunner
NuxeoBatch --> Nuxeo
NuxeoBatch --> HxprApp
NuxeoBatch -.-> ModelRunner
NuxeoLive --> Nuxeo
NuxeoLive --> HxprApp
NuxeoLive -.-> ModelRunner
Rag --> Alfresco
Rag --> Nuxeo
Rag --> HxprApp
Rag -.-> ModelRunner
HxprApp --> Mongo
HxprApp --> OpenSearch
Notes:
proxyis the only public entrypoint for Alfresco, the UI, batch/sync APIs, and RAG APIs.content-appis exposed at/aca/in every profile where it is present (alfresco,full,demo).- In
alfrescoandfullprofiles,/redirects to/aca/. - In
demoprofile,content-lake-app-uiserves/and ACA remains at/aca/. - In
nuxeoprofile,/redirects to/nuxeo/. - The Nuxeo routes are active in
nuxeo,full, anddemoprofiles, and require../nuxeo-deploymentto be running. opensearch-dashboardsis opt-in (debugprofile) and published on port5601, not throughproxy. It is unauthenticated, so keep it off unless you are debugging locally:docker compose --profile demo --profile debug up -d opensearch-dashboards.otel-lgtmis opt-in (observabilityprofile) and published on port3001, not throughproxy. It bundles an OTLP collector, Prometheus, Tempo and a Grafana with anonymous admin access, so it is for local development only. PointMANAGEMENT_OTLP_TRACING_ENDPOINTat it and setRAG_OBSERVABILITY_PAYLOADS_ENABLED=trueto see what a RAG request retrieved and what it spent;RAG_OBSERVABILITY_CAPTURE_CONTENTadditionally exports document content and is off by default.make verify-profilesasserts this and the other opt-in profiles never leak into a base profile.- hxpr needs only
mongodbandopensearch, matching the reference stack inHyland/ai-ready-index(server/hxpr-community-internal-app/docker-compose.yml). The community app runs async tasks in memory and stores blobs locally, so there is no LocalStack (S3/SNS/SQS) or Nucleus mock in this stack. - Alfresco and hxpr share the single
opensearchcluster as two independent indices:alfresco*(repository search, fed bybatch-indexer) andnuxeo_embeddings*(hxpr semantic search). There are no cross-index reads. On a clean deploy the batch indexer starts at "now" and indexes content created while it runs; the archive/trashcan search scope is not supported by the OpenSearch module. - Docker Model Runner is an external dependency used by the Content Lake services, not a Compose service in this repository.
Before redesigning the deployment, the non-negotiable Alfresco-side requirements were:
- Alfresco Repository with the
content-lake-repo-modelmodule socl:indexedandcl:excludeFromLakeexist. - ActiveMQ configured for Alfresco Event2 so
live-ingestercan consumealfresco.repo.event2. - Alfresco Transform Core AIO for text extraction during ingestion.
- Alfresco repository search wired to OpenSearch (the
elasticsearchindex subsystem), fed by thebatch-indexerservice (Alfresco Search Community, ACS 26.2+), withsecureComms=secret. - A reverse proxy exposing
/,/alfresco/,/admin/,/api-explorer/, and/api/rag/.
This repo vendors the required ACS module/config pieces locally and builds the rest of the stack around them.
- Local ACS repository image customization under
acs/alfresco - Vendored HXPR bootstrap assets under
hxpr/ - Local HXPR Docker build that clones and compiles the requested HXPR branch
- Remote builds for
aborroy/content-lake-appandaborroy/alfresco-content-lake-ui - Remote build for
aborroy/content-lake-app-ui(demo profile) -- no local clone needed - Docker Compose orchestration split across five focused
compose.*.yamlfiles - A single nginx config template replacing per-mode nginx files
By default, all Java services and UI images are built by pulling source from GitHub via Docker
BuildKit. No local checkouts are needed except nuxeo-deployment (sibling directory, see
Quick Start).
| Repo | Default source | Local override env var |
|---|---|---|
content-lake-app |
github.com/aborroy/content-lake-app#main |
CONTENT_LAKE_GIT_CONTEXT=../content-lake-app |
alfresco-content-lake-ui |
github.com/aborroy/alfresco-content-lake-ui#main |
CONTENT_LAKE_UI_GIT_CONTEXT=../alfresco-content-lake-ui |
content-lake-app-ui |
github.com/aborroy/content-lake-app-ui#main |
CONTENT_LAKE_APP_UI_CONTEXT=../content-lake-app-ui |
hxpr |
github.com/Hyland/ai-ready-index (branch from HXPR_GIT_REF) |
HXPR_LOCAL_IMAGE=<local-tag> |
nuxeo-deployment |
sibling directory ../nuxeo-deployment (required for Nuxeo profiles) |
-- |
To build everything from local source (useful during active development):
make up-demo localThe local parameter sets all four *_CONTEXT overrides automatically and forces --no-cache so local source changes are always picked up.
- Docker Desktop with Docker Compose v2
- Docker Model Runner -- enable in Docker Desktop settings, or install
docker-model-pluginon Linux - Access to
ghcr.iofor Hyland images - Outbound access to GitHub so BuildKit can fetch the remote source contexts
HXPR_GIT_AUTH_TOKENwhileHyland/ai-ready-indexis private (needed for the source clone)- Optional
MAVEN_USERNAME/MAVEN_PASSWORD/NEXUS_USERNAME/NEXUS_PASSWORDonly if the engine build resolves a private artifact from thehylandsoftware-releasesNexus repo
The engine is built from Hyland/ai-ready-index. That repository is currently private, so a source
clone needs a GitHub token; the Maven build otherwise resolves from public repositories.
HXPR_GIT_AUTH_TOKEN -- required while https://github.com/Hyland/ai-ready-index.git is private.
Use a GitHub classic token with repo scope, or a fine-grained token scoped to Hyland/ai-ready-index
with read access to repository contents.
MAVEN_USERNAME / MAVEN_PASSWORD / NEXUS_USERNAME / NEXUS_PASSWORD -- optional. Only set
them if the build fails resolving a private artifact from the hylandsoftware-releases Nexus repo
(https://artifacts.alfresco.com/nexus/content/repositories/hylandsoftware-releases). When unset,
the build proceeds anonymously.
Every credential in the tracked .env is a local development default and is world-readable, so this
stack is safe to run on a laptop and not safe to expose. Before putting it on any host reachable by
someone else, change at least these:
| Key | Committed default | Why it matters |
|---|---|---|
ALFRESCO_ADMIN_PASSWORD, HXPR_PASSWORD, NUXEO_PASSWORD |
vendor defaults (admin, password, Administrator) |
administrator on the repository and on the engine |
OPENSEARCH_ADMIN_PASSWORD |
a fixed value that satisfies the OpenSearch password policy | looks like a real password but is published here |
SHARED_SECRET |
a fixed string | the Alfresco to Solr shared secret; anyone holding it can talk to Solr as the repository |
POSTGRES_PASSWORD, ACTIVEMQ_PASSWORD |
vendor defaults | direct database and broker access |
CONNECTOR_SYNC_USERNAME and CONNECTOR_SYNC_PASSWORD are deliberately the exception: they ship
empty and the service refuses to start until you supply them, because the endpoint they guard
triggers a full re-ingest. Put overrides for any of the above in .env.local, which is not tracked.
-
Authenticate to GitHub Container Registry:
docker login ghcr.io
-
Enable Docker Model Runner in Docker Desktop.
-
Pull the AI models once:
docker model pull ai/mxbai-embed-large docker model pull ai/qwen2.5
-
Put your credentials in
.env.local(never committed):cat >> .env.local <<'EOF' MAVEN_USERNAME=... MAVEN_PASSWORD=... NEXUS_USERNAME=... NEXUS_PASSWORD=... EOF
-
Start the stack:
make up-alfresco # Alfresco only (most common) make up-full # Alfresco + Nuxeo (requires ../nuxeo-deployment) make up-nuxeo # Nuxeo only make up-demo # demo UI at /
Or use the guided script:
./setup.sh [alfresco|nuxeo|full|demo]
For any profile that includes Nuxeo:
git clone https://github.com/aborroy/nuxeo-deployment.git ../nuxeo-deployment
(cd ../nuxeo-deployment && docker compose up -d)
make up-fullIf http://localhost/nuxeo/ui returns 502 Bad Gateway, check that ../nuxeo-deployment is running and reachable on port 8081.
Only the proxy is published on the host on port 80.
| URL | Available in profiles |
|---|---|
http://localhost/ |
Redirects to /aca/ (alfresco/full), /nuxeo/ (nuxeo), or demo UI (demo) |
http://localhost/aca/ |
alfresco, full, demo |
http://localhost/alfresco/ |
alfresco, full, demo |
http://localhost/admin/ |
alfresco, full, demo |
http://localhost/api-explorer/ |
alfresco, full, demo |
http://localhost/nuxeo/ |
nuxeo, full, demo |
http://localhost/api/rag/ |
all profiles |
http://localhost/api/content-lake/ |
alfresco, full, demo |
http://localhost/api/sync/ |
all profiles (routes to the alfresco or nuxeo ingester via ?sourceType=) |
http://localhost:5601/ |
OpenSearch Dashboards, debug profile only (opt-in, unauthenticated) |
To seed a sample file in the local Nuxeo stack without using the Web UI,
start ../nuxeo-deployment and run a Nuxeo-enabled profile, then use
scripts/create-nuxeo-demo-file.sh:
./scripts/create-nuxeo-demo-file.sh
./scripts/create-nuxeo-demo-file.sh --title "Quarterly Notes" --text $'Line 1\nLine 2'
./scripts/create-nuxeo-demo-file.sh --input-file README.md --mime-type text/markdownTo verify indexing afterwards:
curl -u Administrator:Administrator -X POST 'http://localhost/api/sync/configured?sourceType=nuxeo'Defaults live in .env. To override locally, create .env.local with only the variables you want to change:
# Example .env.local
HXPR_GIT_REF=main
PUBLIC_PORT=9090.env.local is listed in .gitignore and is never committed.
Note: Docker Compose only auto-loads
.env. The Makefile passes--env-file .env.localautomatically when the file exists. If you rundocker composedirectly, add the flag yourself.
Key overrides:
| Variable | Default | Description |
|---|---|---|
HXPR_GIT_URL |
https://github.com/Hyland/ai-ready-index.git |
Engine source repo |
HXPR_GIT_REF |
main |
Branch or tag to build |
HXPR_GIT_SHA |
(empty) | Pin to a specific commit SHA for reproducible builds |
HXPR_LOCAL_IMAGE |
content-lake-app/hxpr-app:local |
Local image tag for the built HXPR app |
CONTENT_LAKE_GIT_CONTEXT |
https://github.com/aborroy/content-lake-app.git#main |
Java source context |
CONTENT_LAKE_UI_GIT_CONTEXT |
https://github.com/aborroy/alfresco-content-lake-ui.git#main |
ACA extension context |
CONTENT_LAKE_APP_UI_CONTEXT |
https://github.com/aborroy/content-lake-app-ui.git#main |
Demo UI context (override to ../content-lake-app-ui for local dev) |
ACA_TAG |
7.4.1 |
Alfresco Content App version |
PUBLIC_PORT |
80 |
Host port for the proxy |
DEMO_UI_PORT |
4200 |
Direct host port for the demo UI container |
MODEL_RUNNER_URL |
http://model-runner.docker.internal |
LLM/embedding inference backend |
EMBEDDING_MODEL |
ai/mxbai-embed-large |
Embedding model |
LLM_MODEL |
ai/qwen2.5 |
Chat/RAG model |
EXTRACTION_FORMAT |
plaintext |
plaintext, auto or markdown. Whether extraction asks a transform engine for markdown, so headings and tables survive chunking. See docs/extraction.md |
TRANSFORM_URL |
http://transform-core-aio:8090 |
Transform engine for the Alfresco ingesters. Point at http://transform-liteparse:8090 or http://transform-convert2md:8090 with the transform-extras profile |
EXTRACTION_ENGINE_URL |
(empty) | Transform engine for the Nuxeo ingesters and any plugin connector, which have none by default. Empty leaves them on in-process Tika |
EXTRACTION_ENGINE_TIMEOUT_MS |
300000 |
Read timeout for the above. Do not lower it: convert2md needs tens of seconds per PDF |
TRANSFORM_EXTRAS_TAG |
1.1.0 |
transform-liteparse image tag. 1.1.0 or newer is required for spreadsheet tables |
TRANSFORM_CONVERT2MD_TAG |
1.1.0 |
transform-convert2md image tag |
On Linux, override MODEL_RUNNER_URL=http://host.docker.internal:12434 in .env.local.
make up-alfresco # build and start Alfresco profile
make up-nuxeo # build and start Nuxeo profile
make up-full # build and start full profile
make up-demo # build and start demo profile
make down # stop and remove containers (volumes preserved)
make logs # follow logs for all services
make ps # show running services and health
make config # render the resolved compose configuration
make clean # stop + remove all volumes [DESTRUCTIVE]You can also call docker compose directly; remember to add --env-file .env.local and --profile <name> explicitly.
test/smoke-test.sh is a self-contained end-to-end smoke test that runs against any live
deployment -- local or EC2 -- without stopping services or touching existing data. The
environment is left identical to its state before the test: every document, user, workspace,
and log file created during the run is deleted before the script exits.
- After deploying a new build -- confirms the full ingest-to-search pipeline is intact.
- After any configuration change -- credentials, compose overrides, nginx rules, etc.
- After an EC2 restart -- verifies all services came back up healthy.
- Before a demo -- quick sanity check that the stack is working end to end.
| Section | What is verified |
|---|---|
| A -- Service health | RAG service UP (embedding, hxpr, LLM sub-components), Alfresco responds, Nuxeo responds, unauthenticated request returns 401, sync API reachable |
| B -- Alfresco batch ingest | Creates a folder + document, triggers /api/sync/batch, waits for sync completion and embedding, verifies the document appears in hybrid search |
| C -- Nuxeo live ingest | Creates a Nuxeo document, waits for the audit-poll cycle, verifies it appears in hybrid search |
| D -- Cross-source search | A single query returns results from both Alfresco and Nuxeo in one response |
| E -- Apostrophe regression | Query containing ' (e.g. "king arthur's legend") completes without a NXQL parse error |
| F -- RAG prompt | /api/rag/prompt returns a non-empty LLM answer with source citations |
| F2 -- Semantic search | /api/rag/search/semantic (vector-only path used by the demo app search panel) returns the Alfresco fixture |
| F3 -- Source-type filter | sourceType=alfresco on hybrid search returns the Alfresco doc and excludes the Nuxeo doc |
| F4 -- Streaming chat | /api/rag/chat/stream (SSE endpoint used by the demo chat UI) opens and emits data lines |
| F5 -- Node status | /api/content-lake/nodes/{id}/status returns a status field (used by the ACA extension) |
| G -- Cleanup + delete propagation | All created documents, users, and workspaces are deleted; Alfresco and Nuxeo docs disappear from search after deletion; log file is removed |
All credentials must be supplied as environment variables -- no defaults are hardcoded.
Local stack:
HOST=localhost \
ALF_AUTH=admin:admin \
NUXEO_AUTH=Administrator:Administrator \
NUXEO_PORT=8081 \
NUXEO_WORKSPACE=content-lake-smoke \
./test/smoke-test.shEC2 (or any remote host):
HOST=archer.alfdemo.com \
ALF_AUTH=admin:<alfresco-password> \
NUXEO_AUTH=Administrator:<nuxeo-password> \
./test/smoke-test.sh| Variable | Required | Default | Description |
|---|---|---|---|
ALF_AUTH |
yes | -- | Alfresco admin credentials (user:password) |
NUXEO_AUTH |
yes | -- | Nuxeo admin credentials (user:password) |
HOST |
no | localhost |
Target hostname or IP |
NUXEO_PORT |
no | 80 |
Nuxeo port (80 = through nginx proxy; 8081 = direct, local only) |
NUXEO_WORKSPACE |
no | content-lake-smoke |
Nuxeo workspace used for test documents -- created automatically if absent. content-lake-smoke is treated as a disposable smoke workspace and deleted during cleanup even if it existed before the run. |
WAIT_LIVE_S |
no | 60 |
Seconds to wait for the Nuxeo live-ingester audit poll |
WAIT_EMBED_S |
no | 30 |
Seconds to wait for the embedding pipeline after Alfresco sync |
TOPK |
no | 30 |
topK used for presence checks in search results |
Passed : 28
Failed : 0
A non-zero Failed count means at least one pipeline stage is broken. The script writes a
smoke-test-<timestamp>.log file during the run containing the full output including top-3
search results for every failed assertion; this file is deleted at the end of a successful
run. If the script is interrupted or exits with failures, the log file is kept for inspection.
test/test-connector.sh proves the plugin path end to end: it builds the sample connector from
../content-lake-app/plugins/examples/sample-directory-connector inside a Maven container,
drops the jar into connectors/, starts plugin-batch-ingester on top of a base stack that is already
running, triggers a sync and asserts the fixture documents come back out of semantic search.
Opt-in, like the profile it exercises, so it is not a phase of test/run-tests.sh. It needs a base stack
up, the AI backend on :12434, and Docker.
CONNECTOR_SYNC_USERNAME=admin CONNECTOR_SYNC_PASSWORD=admin \
RAG_AUTH=admin:admin ./test/test-connector.shIt removes the service and the jar on exit; pass KEEP_RUNNING=true to keep both for poking at.
test/test-cmis.sh is the same shape for the shipped CMIS connector
(../content-lake-app/plugins/cmis-connector), and it checks the one thing a generic adapter has to be
held to: that it ingests what the purpose-built adapter ingests. It creates a folder of fixtures in the
running Alfresco, syncs it with the native adapter, then syncs the same folder over Alfresco's own CMIS
endpoint and compares the two document sets. It also restricts one fixture to admin with inheritance off
and asserts that a second user cannot retrieve it while the folder's public fixture stays readable, which is
the ACL mapping working rather than being skipped.
Unlike test-connector.sh it pins no RAG_PERMISSION_SOURCE_IDS: since #133 the permission filter
discovers every source in the index, so a pin would mask exactly what needs proving.
CONNECTOR_SYNC_USERNAME=admin CONNECTOR_SYNC_PASSWORD=admin \
RAG_AUTH=admin:admin ./test/test-cmis.shOne case it deliberately does not cover, and says so in its own output: a repository reporting
capabilityACL=NONE. Alfresco reports manage, so the fail-closed refusal and the sync-account and
public fallbacks are unit-tested in the connector instead.
test/test-sharepoint.sh is the same shape again for the shipped SharePoint connector
(../content-lake-app/plugins/sharepoint-connector), run against the sharepoint-mock profile rather than
a tenant. The mock is the target by necessity, not convenience: registering an application is disabled in
the tenant available here, and a SharePoint site a developer is only a member of returns a truncated
ACL, so it cannot validate permission mapping at all. Exactly two things differ from the cloud, the Graph
base URL and the token provider, and both are configuration.
CONNECTOR_SYNC_USERNAME=admin CONNECTOR_SYNC_PASSWORD=admin \
RAG_AUTH=admin:admin ./test/test-sharepoint.shThree passes over the same fixture tree: a walk, an incremental pass through the change feed that applies a
tombstone, and a third in hierarchical permissions mode under a second source id. It also creates two
Alfresco callers and switches rag-service's Entra group resolver on, restoring it afterwards, because the
assertions that matter are about whether an ACL is actionable rather than merely recorded:
- a document granted to one named user is not retrievable by another, in either permissions mode;
- a document granted only to an Entra group is retrievable by a member of that group and is not by anyone else;
- a
users-scoped sharing link grants the named identities and nobody else; - the cost per document is reported in both permissions modes and compared, rather than estimated;
- with the mock honouring no
Preferheader,hierarchicalmode refuses instead of silently paying the per-item price.
What no mock run can establish, and what still needs a tenant: real payload fidelity beyond the fixtures
(written from Microsoft's documented shapes, not recorded), app-only token acquisition, genuine throttling
behaviour, and whether SharePoint honours the Prefer headers at all. Do not read a green run as evidence
of any of those four.
See docs/DEPLOY_EC2.md for a step-by-step guide to running the full stack on a g5.2xlarge (8 vCPU / 32 GB RAM / NVIDIA A10G GPU, 24 GB VRAM) Ubuntu instance, including vLLM, TEI, and nginx proxy installation for GPU-accelerated inference.
- The engine is built from source (
Hyland/ai-ready-index) duringdocker compose up --buildusingHXPR_GIT_REF(default:main). - The source clone needs
HXPR_GIT_AUTH_TOKENwhile the repo is private; the Maven build otherwise resolves from public repositories (Nexus/GitHub-Packages credentials are optional build secrets, used only if a private artifact is required). - content-lake-app authenticates to the engine with HTTP Basic (
HXPR_USERNAME/HXPR_PASSWORD). - All Content Lake Java services (
batch-ingester,live-ingester, ingesters,rag-service) build from source fetched directly from GitHub -- no local Java checkout needed. - The repository model is injected directly into the Alfresco image from this repo.
- The ACA UI is exposed at
/aca/in every profile where it is enabled, so its context path stays stable across stacks. - The demo UI (
content-lake-app-ui) is served at/only in thedemoprofile. - The demo UI is a demonstration, not a reference authentication implementation. It persists the
Alfresco ticket (revocable) but holds the Nuxeo
base64(user:pass)credential in memory only, so reloading the page keeps the Alfresco session and ends the Nuxeo one.
This repo builds the main branch of Hyland/ai-ready-index. While that repository is private the source clone needs HXPR_GIT_AUTH_TOKEN. To build a different branch or repo URL, override HXPR_GIT_URL and HXPR_GIT_REF in .env.local.