diff --git a/apps/ragflow/config.json b/apps/ragflow/config.json new file mode 100644 index 0000000..c5e569b --- /dev/null +++ b/apps/ragflow/config.json @@ -0,0 +1,69 @@ +{ + "$schema": "../app-info-schema.json", + "name": "RAGFlow", + "id": "ragflow", + "available": true, + "short_desc": "Open-source RAG engine with deep document understanding", + "author": "alexz", + "port": 9380, + "categories": ["ai"], + "tipi_version": 1, + "version": "0.26.4", + "source": "https://github.com/infiniflow/ragflow", + "website": "https://ragflow.io", + "exposable": true, + "dynamic_config": true, + "supported_architectures": ["amd64"], + "min_tipi_version": "4.0.0", + "form_fields": [ + { + "type": "random", + "label": "MySQL Root Password", + "hint": "Root password for the bundled MySQL 8.0 database that stores RAGFlow's metadata", + "env_variable": "RAGFLOW_MYSQL_ROOT_PASSWORD", + "min": 24, + "encoding": "hex" + }, + { + "type": "random", + "label": "Elasticsearch Password", + "hint": "Password for the built-in 'elastic' user on the Elasticsearch 8.11.3 document engine", + "env_variable": "RAGFLOW_ELASTIC_PASSWORD", + "min": 24, + "encoding": "hex" + }, + { + "type": "random", + "label": "MinIO Root Password", + "hint": "Root password for the bundled MinIO object storage service (file/blob storage)", + "env_variable": "RAGFLOW_MINIO_ROOT_PASSWORD", + "min": 24, + "encoding": "hex" + }, + { + "type": "random", + "label": "Redis Password", + "hint": "Password (requirepass) for the bundled Valkey/Redis cache and task queue", + "env_variable": "RAGFLOW_REDIS_PASSWORD", + "min": 24, + "encoding": "hex" + }, + { + "type": "random", + "label": "Session/JWT Signing Secret", + "hint": "Signs RAGFlow's session cookies and API JWTs (RAGFLOW_SECRET_KEY). Without this, the image auto-generates and persists a secret in Redis on first boot, but a per-install random value here is stronger and survives a Redis data wipe.", + "env_variable": "RAGFLOW_SECRET_KEY", + "min": 32, + "encoding": "hex" + }, + { + "type": "boolean", + "label": "Enable Self-Registration", + "hint": "Leave ON while this app is LAN-only so you can create the first admin account. Once the admin account exists, turn this OFF (re-save this form) BEFORE exposing the app on a public domain — see description for the full bootstrap sequence.", + "env_variable": "RAGFLOW_REGISTER_ENABLED", + "default": true + } + ], + "created_at": 1784530800000, + "updated_at": 1784553280814 +} diff --git a/apps/ragflow/docker-compose.json b/apps/ragflow/docker-compose.json new file mode 100644 index 0000000..8370a41 --- /dev/null +++ b/apps/ragflow/docker-compose.json @@ -0,0 +1,196 @@ +{ + "schemaVersion": 2, + "$schema": "https://schemas.runtipi.io/v2/dynamic-compose.json", + "services": [ + { + "name": "ragflow-es-init", + "image": "busybox:1.36", + "isMain": false, + "restart": "no", + "user": "root", + "command": ["chown", "-R", "1000:0", "/usr/share/elasticsearch/data"], + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data/es", + "containerPath": "/usr/share/elasticsearch/data", + "readOnly": false + } + ] + }, + { + "name": "ragflow-es01", + "image": "elasticsearch:8.11.3", + "environment": [ + { "key": "node.name", "value": "es01" }, + { "key": "ELASTIC_PASSWORD", "value": "${RAGFLOW_ELASTIC_PASSWORD}" }, + { "key": "bootstrap.memory_lock", "value": "false" }, + { "key": "discovery.type", "value": "single-node" }, + { "key": "xpack.security.enabled", "value": "true" }, + { "key": "xpack.security.http.ssl.enabled", "value": "false" }, + { "key": "xpack.security.transport.ssl.enabled", "value": "false" }, + { "key": "cluster.routing.allocation.disk.watermark.low", "value": "5gb" }, + { "key": "cluster.routing.allocation.disk.watermark.high", "value": "3gb" }, + { "key": "cluster.routing.allocation.disk.watermark.flood_stage", "value": "2gb" }, + { "key": "ES_JAVA_OPTS", "value": "-Xms2g -Xmx4g" } + ], + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data/es", + "containerPath": "/usr/share/elasticsearch/data", + "readOnly": false + } + ], + "ulimits": { + "memlock": { "soft": -1, "hard": -1 } + }, + "deploy": { + "resources": { + "limits": { + "memory": "8000M" + } + } + }, + "dependsOn": { + "ragflow-es-init": { "condition": "service_completed_successfully" } + }, + "healthCheck": { + "test": "curl http://127.0.0.1:9200 || exit 1", + "interval": "10s", + "timeout": "10s", + "retries": 120, + "startPeriod": "120s" + } + }, + { + "name": "ragflow-mysql", + "image": "mysql:8.0.39", + "environment": [ + { "key": "MYSQL_ROOT_PASSWORD", "value": "${RAGFLOW_MYSQL_ROOT_PASSWORD}" }, + { "key": "MYSQL_DATABASE", "value": "rag_flow" } + ], + "command": [ + "mysqld", + "--max_connections=1000", + "--character-set-server=utf8mb4", + "--collation-server=utf8mb4_unicode_ci", + "--default-authentication-plugin=mysql_native_password", + "--tls_version=TLSv1.2,TLSv1.3", + "--binlog_expire_logs_seconds=604800" + ], + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data/mysql", + "containerPath": "/var/lib/mysql", + "readOnly": false + } + ], + "healthCheck": { + "test": "mysqladmin ping -uroot -p${RAGFLOW_MYSQL_ROOT_PASSWORD}", + "interval": "10s", + "timeout": "10s", + "retries": 60, + "startPeriod": "30s" + } + }, + { + "name": "ragflow-minio", + "image": "pgsty/minio:RELEASE.2026-03-25T00-00-00Z", + "command": ["server", "--console-address", ":9001", "/data"], + "environment": [ + { "key": "MINIO_ROOT_USER", "value": "rag_flow" }, + { "key": "MINIO_ROOT_PASSWORD", "value": "${RAGFLOW_MINIO_ROOT_PASSWORD}" } + ], + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data/minio", + "containerPath": "/data", + "readOnly": false + } + ], + "healthCheck": { + "test": "curl -f http://127.0.0.1:9000/minio/health/live || exit 1", + "interval": "10s", + "timeout": "10s", + "retries": 60, + "startPeriod": "30s" + } + }, + { + "name": "ragflow-redis", + "image": "valkey/valkey:8.0.2", + "command": [ + "redis-server", + "--requirepass", "${RAGFLOW_REDIS_PASSWORD}", + "--maxmemory", "128mb", + "--maxmemory-policy", "allkeys-lru" + ], + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data/redis", + "containerPath": "/data", + "readOnly": false + } + ], + "healthCheck": { + "test": "redis-cli -a ${RAGFLOW_REDIS_PASSWORD} ping", + "interval": "10s", + "timeout": "10s", + "retries": 30, + "startPeriod": "15s" + } + }, + { + "name": "ragflow-server", + "image": "infiniflow/ragflow:v0.26.4", + "isMain": true, + "internalPort": 80, + "entrypoint": [ + "/bin/bash", + "-c", + "if [ \"$RAGFLOW_REGISTER_ENABLED\" = \"false\" ]; then export REGISTER_ENABLED=0; else export REGISTER_ENABLED=1; fi; exec ./entrypoint.sh \"$@\"", + "bash" + ], + "command": ["--enable-adminserver", "--init-model-provider-tables"], + "environment": [ + { "key": "TZ", "value": "${TZ}" }, + { "key": "DOC_ENGINE", "value": "elasticsearch" }, + { "key": "ES_HOST", "value": "ragflow-es01" }, + { "key": "ELASTIC_PASSWORD", "value": "${RAGFLOW_ELASTIC_PASSWORD}" }, + { "key": "MYSQL_HOST", "value": "ragflow-mysql" }, + { "key": "MYSQL_PORT", "value": "3306" }, + { "key": "MYSQL_DBNAME", "value": "rag_flow" }, + { "key": "MYSQL_PASSWORD", "value": "${RAGFLOW_MYSQL_ROOT_PASSWORD}" }, + { "key": "MINIO_HOST", "value": "ragflow-minio" }, + { "key": "MINIO_USER", "value": "rag_flow" }, + { "key": "MINIO_PASSWORD", "value": "${RAGFLOW_MINIO_ROOT_PASSWORD}" }, + { "key": "REDIS_HOST", "value": "ragflow-redis" }, + { "key": "REDIS_PASSWORD", "value": "${RAGFLOW_REDIS_PASSWORD}" }, + { "key": "RAGFLOW_SECRET_KEY", "value": "${RAGFLOW_SECRET_KEY}" }, + { "key": "RAGFLOW_REGISTER_ENABLED", "value": "${RAGFLOW_REGISTER_ENABLED}" }, + { "key": "API_PROXY_SCHEME", "value": "python" }, + { "key": "USE_DOCLING", "value": "false" }, + { "key": "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "value": "1" } + ], + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data/logs", + "containerPath": "/ragflow/logs", + "readOnly": false + } + ], + "dependsOn": { + "ragflow-es01": { "condition": "service_healthy" }, + "ragflow-mysql": { "condition": "service_healthy" }, + "ragflow-minio": { "condition": "service_healthy" }, + "ragflow-redis": { "condition": "service_healthy" } + }, + "healthCheck": { + "test": "curl -f http://127.0.0.1:80/ || exit 1", + "interval": "30s", + "timeout": "10s", + "retries": 10, + "startPeriod": "180s" + } + } + ] +} diff --git a/apps/ragflow/metadata/description.md b/apps/ragflow/metadata/description.md new file mode 100644 index 0000000..b7faf44 --- /dev/null +++ b/apps/ragflow/metadata/description.md @@ -0,0 +1,80 @@ +# RAGFlow + +RAGFlow is an open-source Retrieval-Augmented Generation (RAG) engine built on +deep document understanding. It combines robust document layout parsing (PDF, +Word, spreadsheets, slides, images, and more) with a configurable retrieval +pipeline so you can ground LLM answers in your own knowledge base with +traceable citations. + +## What's included + +This app deploys the full self-hosted RAGFlow stack in one shot: + +- **RAGFlow server** (`infiniflow/ragflow:v0.26.4`) — the web UI, HTTP API, + admin server, and task executor. This is the version-pinned image RAGFlow + ships as the lightweight variant since v0.22 (no bundled embedding models). +- **Elasticsearch 8.11.3** — the document/vector engine used for full-text and + hybrid search over parsed document chunks. A one-shot `ragflow-es-init` + helper container runs before it to fix ownership of the data directory + (Elasticsearch's image runs as uid 1000, but Docker auto-creates bind-mount + directories as root; without this step Elasticsearch crash-loops on first + install and the whole stack never comes up). +- **MySQL 8.0** — relational metadata store (users, datasets, chat sessions, + configuration). The `rag_flow` database is created automatically via + `MYSQL_DATABASE` on first boot. +- **MinIO** — S3-compatible object storage for uploaded documents and + generated artifacts. +- **Valkey (Redis-compatible) 8.0.2** — cache and task queue for the ingestion + pipeline. + +## Models + +No LLM or embedding model is bundled or hard-coded into this deployment. +After installing, open **Settings → Model Providers** in the RAGFlow UI and +add an **Ollama** provider pointing at your existing Ollama server on this +instance: + +- Base URL: `http://ollama-nvidia:11434` +- Fallback (if the container-name route doesn't resolve): `http://172.18.0.1:11434` + +## Admin bootstrap and public-exposure sequence (read before exposing) + +This app installs with **self-registration ON** by default so you can create +the first (admin) account. Follow this sequence exactly: + +1. Install and wait for `ragflow-server` to report healthy. +2. Visit the app on the LAN (`http://`), register the + first account — this becomes the admin account — and confirm you can sign + in and reach the dashboard. +3. Open this app's settings in the Runtipi dashboard and turn **Enable + Self-Registration** OFF, then save. This re-applies the app config with + `RAGFLOW_REGISTER_ENABLED=false`, which is translated internally to + RAGFlow's `REGISTER_ENABLED=0`. +4. Only after step 3 is confirmed, assign a public domain + (`ragflow.alexzaw.dev`) and enable Traefik exposure from the dashboard. + +Do not skip step 3 before going public — leaving self-registration on for an +internet-facing instance lets anyone create an account. + +## Session/JWT signing secret + +A random 32-byte hex value is generated at install time and passed as +`RAGFLOW_SECRET_KEY`, which RAGFlow uses directly (`common/settings.py: +init_secret_key()`) to sign session cookies and JWTs, as long as it's at +least 32 characters — ours is 64 hex characters. If this field were ever +left empty, RAGFlow's own fallback still applies: it auto-generates a secret +and persists it in Redis (`ragflow:system:secret_key`), so sessions survive +container restarts either way. The explicit field is stronger because it +doesn't depend on Redis data surviving a wipe/reset. + +## Residual public-exposure risk + +RAGFlow has no built-in 2FA and its own auth surface has not been +independently audited by this deployment. Before assigning the public +domain, put this app behind Cloudflare Access or an authentik forward-auth +gate at the network layer, in addition to the registration lockdown above. + +## Links + +- Source: https://github.com/infiniflow/ragflow +- Docs: https://ragflow.io/docs diff --git a/apps/ragflow/metadata/logo.jpg b/apps/ragflow/metadata/logo.jpg new file mode 100644 index 0000000..b210edb Binary files /dev/null and b/apps/ragflow/metadata/logo.jpg differ