diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d9aab8b --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# PostgreSQL Configuration +POSTGRES_USER=temporal +POSTGRES_PASSWORD=temporal +POSTGRES_DB=temporal +POSTGRES_PORT=5432 + +# n8n Configuration +N8N_WEBHOOK_URL=http://localhost:5678/ +N8N_ENCRYPTION_KEY=a_random_string_for_encryption +N8N_PORT=5678 + +# OpenSearch Configuration +DISABLE_SECURITY_PLUGIN=true +OPENSEARCH_PORT=9200 + +# Temporal Configuration +TEMPORAL_PORT=7233 +TEMPORAL_UI_PORT=8080 + +# Volumes path (you can customize if needed) +PWD=. diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 933c2ae..58d8f92 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -12,19 +12,54 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout + - name: Checkout code uses: actions/checkout@v4 - - name: Lint + - name: Run linting run: echo "Linting..." sonarqube: name: SonarQube runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: SonarQube Scan + - name: Run SonarQube scan uses: SonarSource/sonarqube-scan-action@v5 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + hadolint: + name: Dockerfile Linting + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Lint Dockerfile.n8n + run: docker run --rm -i hadolint/hadolint < Dockerfile.n8n + - name: Lint Dockerfile.temporal + run: docker run --rm -i hadolint/hadolint < Dockerfile.temporal + + service-check: + name: Service Availability Check + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Create volume directories + run: bash scripts/setup_volumes.sh + - name: Copy .env file + run: cp .env.example .env + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build services with no cache + run: docker compose build --no-cache + - name: Start services + run: docker compose up -d + - name: Verify services + run: bash scripts/check_services.sh + - name: Stop services + if: always() + run: docker compose down -v diff --git a/.gitignore b/.gitignore index 1170717..66b3b1b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,11 @@ yarn-error.log* lerna-debug.log* .pnpm-debug.log* +# mcp executable +mcp +mcp-config.json +.cursor/ + # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json @@ -134,3 +139,4 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* +volumes diff --git a/Dockerfile.n8n b/Dockerfile.n8n new file mode 100644 index 0000000..08eddb2 --- /dev/null +++ b/Dockerfile.n8n @@ -0,0 +1,20 @@ +FROM n8nio/n8n:1.89.2 + +# Define build arguments +ARG NODE_ENV=production +ARG N8N_PORT=5678 +# Environment variables are now defined in docker-compose.yml + +# Create app directory +WORKDIR /home/node + +# Add custom healthcheck using exec form +HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=30s \ + CMD ["/bin/sh", "-c", "wget -q --spider http://0.0.0.0:${N8N_PORT}/healthz || exit 1"] + +# Explicitly set the user to the non-root 'node' user (which is already set up in the base image) +USER node + +EXPOSE ${N8N_PORT} +# The entrypoint script is already defined in the base image +# Don't override the CMD diff --git a/Dockerfile.temporal b/Dockerfile.temporal new file mode 100644 index 0000000..0b87324 --- /dev/null +++ b/Dockerfile.temporal @@ -0,0 +1,20 @@ +FROM temporalio/auto-setup:1.20.5 + +# Build arguments are still needed for the temporal container setup +# Keeping only those used in the HEALTHCHECK or other commands +ARG HOST=temporal +ARG TEMPORAL_PORT=7233 + +# Environment variables are now defined in docker-compose.yml + +# Add custom healthcheck using exec form +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD ["/bin/sh", "-c", "temporal operator cluster health --address ${HOST}:${TEMPORAL_PORT} | grep -q SERVING || exit 1"] + +# Explicitly set the user to the non-root 'temporal' user (already defined in the base image) +USER temporal + +# Expose the gRPC port +EXPOSE ${TEMPORAL_PORT} + +# The entrypoint script is already defined in the base image diff --git a/README.md b/README.md index 40862b3..c243a71 100644 --- a/README.md +++ b/README.md @@ -1 +1,173 @@ -# automatization \ No newline at end of file +# n8n and Temporal Docker Compose Setup + +This repository contains a Docker Compose configuration to run n8n and Temporal services together. + +## Services + +The setup includes: + +- **n8n**: An automation tool that allows you to create workflows visually +- **Temporal**: A workflow orchestration platform with the following components: + - Temporal server + - Temporal UI + - PostgreSQL (database) + - OpenSearch (for visibility features) + +## Custom Docker Images + +This project uses custom Docker images built from the following Dockerfiles: + +- **Dockerfile.n8n**: Extends the official n8n image with custom configurations +- **Dockerfile.temporal**: Extends the official Temporal auto-setup image + +## Usage + +### Prepare volume directories + +Before starting the services, run the setup script to create the necessary volume directories: + +```bash +./scripts/setup_volumes.sh +``` + +This prevents volume mount errors that may occur if the directories don't exist. + +### Create environment file + +Create a `.env` file in the root directory of the project with your environment variables: + +```bash +cp .env.example .env +``` + +Then edit the `.env` file to set your specific configuration values. + +### Starting the services + +```bash +docker compose up -d +``` + +This will start all services in detached mode. + +### Building custom images + +Rebuild images after modifying the Dockerfiles: + +```bash +docker compose build +``` + +Or to rebuild and start in one command: + +```bash +docker compose down && docker compose build && docker compose up -d +``` + +### Verifying services are running + +Check that all containers are running: + +```bash +docker compose ps +``` + +You should see containers for: +- n8n +- temporal +- temporal-ui +- temporal-postgresql +- opensearch + +### Checking Service Health + +Use the provided script to verify that all services are accessible: + +```bash +scripts/check_services.sh +``` + +This will check: +- n8n health endpoint +- Temporal UI web interface +- OpenSearch API +- Temporal server gRPC port +- PostgreSQL database connection + +Example output: +```text +Checking service availability... +Checking n8n at http://localhost:5678/healthz... ACCESSIBLE ✅ (HTTP 200) +Checking temporal-ui at http://localhost:8080... ACCESSIBLE ✅ (HTTP 200) +Checking opensearch at http://localhost:9200... ACCESSIBLE ✅ (HTTP 200) +Checking temporal at localhost:7233... ACCESSIBLE ✅ +Checking postgresql at localhost:5432... ACCESSIBLE ✅ + +Service URLs: +- n8n: http://localhost:5678 +- Temporal UI: http://localhost:8080 +- OpenSearch: http://localhost:9200 +``` + +### Accessing the services + +- **n8n**: +- **Temporal UI**: + +You can verify the services are responding with: + +```bash +# Check n8n is responding +curl -I http://localhost:5678 + +# Check Temporal UI is responding +curl -I http://localhost:8080 +``` + +### Stopping the services + +```bash +docker compose down +``` + +To completely remove all data volumes: + +```bash +docker compose down -v +``` + +## Data Persistence + +All data is stored in local volumes under the `./volumes/` directory: + +- `./volumes/n8n_data` - n8n data and workflows +- `./volumes/opensearch-data` - OpenSearch data for Temporal +- `./volumes/postgresql-data` - PostgreSQL database for Temporal + +## Service Ports + +- n8n: 5678 +- Temporal server: 7233 (gRPC API, not HTTP) +- Temporal UI: 8080 +- PostgreSQL: 5432 +- OpenSearch: 9200 + +## Troubleshooting + +If you encounter any issues: + +1. Check container logs: + ```bash + docker logs temporal + docker logs automatization-n8n-1 + ``` + +2. Ensure all required ports are available on your system + +3. Make sure Docker has sufficient resources allocated + +4. If you encounter volume mount errors (e.g., "failed to mount local volume ... no such file or directory"), run the setup script: + ```bash + ./scripts/setup_volumes.sh + ``` + This creates the necessary volume directories in the `./volumes/` folder. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d3e1cdb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,152 @@ +services: + # n8n service + n8n: + container_name: n8n + build: + context: . + dockerfile: Dockerfile.n8n + restart: unless-stopped + ports: + - "${N8N_PORT}:5678" + environment: + - WEBHOOK_URL=${N8N_WEBHOOK_URL} + - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} + - "N8N_RUNNERS_ENABLED=true" + - "N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true" + - "N8N_PORT=5678" + - "NODE_ENV=production" + - "N8N_METRICS=true" + - "N8N_HEALTH_CHECK_ENDPOINT=true" + volumes: + - n8n_data:/home/node/.n8n + networks: + - app-network + user: node + + # Temporal services + opensearch: + container_name: opensearch + image: opensearchproject/opensearch:2.5.0 + restart: unless-stopped + environment: + - discovery.type=single-node + - bootstrap.memory_lock=true + - "OPENSEARCH_JAVA_OPTS=-Xms256m -Xmx256m" + - "DISABLE_SECURITY_PLUGIN=${DISABLE_SECURITY_PLUGIN}" + - "DISABLE_INSTALL_DEMO_CONFIG=true" + ports: + - ${OPENSEARCH_PORT}:9200 + cap_add: + - IPC_LOCK + ulimits: + memlock: + soft: -1 + hard: -1 + volumes: + - opensearch-data:/usr/share/opensearch/data + networks: + - app-network + healthcheck: + test: ["CMD-SHELL", "curl -sSf http://localhost:${OPENSEARCH_PORT}/ || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 30s + + postgresql: + container_name: temporal-postgresql + image: postgres:14 + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - ${POSTGRES_PORT}:5432 + volumes: + - postgresql-data:/var/lib/postgresql/data + networks: + - app-network + user: postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 30s + + temporal: + container_name: temporal + build: + context: . + dockerfile: Dockerfile.temporal + args: + - HOST=temporal + - TEMPORAL_PORT=${TEMPORAL_PORT} + restart: unless-stopped + depends_on: + postgresql: + condition: service_healthy + opensearch: + condition: service_healthy + environment: + - ES_SEEDS=opensearch + - ES_VERSION=v7 + - DB=postgresql + - DB_PORT=${POSTGRES_PORT} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PWD=${POSTGRES_PASSWORD} + - POSTGRES_SEEDS=postgresql + - ENABLE_ES=true + - HOST=temporal + - TEMPORAL_PORT=7233 + ports: + - ${TEMPORAL_PORT}:7233 + networks: + - app-network + user: temporal + + temporal-ui: + container_name: temporal-ui + image: temporalio/ui:2.10.3 + restart: unless-stopped + depends_on: + temporal: + condition: service_healthy + environment: + - TEMPORAL_ADDRESS=temporal:${TEMPORAL_PORT} + - TEMPORAL_PERMIT_WRITE_API=true + ports: + - ${TEMPORAL_UI_PORT}:8080 + networks: + - app-network + healthcheck: + test: ["CMD", "wget", "-O", "/dev/null", "-q", "http://temporal-ui:${TEMPORAL_UI_PORT}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + +volumes: + n8n_data: + driver: local + driver_opts: + type: none + o: bind + device: ${PWD}/volumes/n8n_data + opensearch-data: + driver: local + driver_opts: + type: none + o: bind + device: ${PWD}/volumes/opensearch-data + postgresql-data: + driver: local + driver_opts: + type: none + o: bind + device: ${PWD}/volumes/postgresql-data + +networks: + app-network: + driver: bridge diff --git a/scripts/check_services.sh b/scripts/check_services.sh new file mode 100755 index 0000000..69e95e2 --- /dev/null +++ b/scripts/check_services.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Source environment variables from .env file +if [ -f .env ]; then + source .env +else + echo "Error: .env file not found. Please make sure it exists in the root directory." + exit 1 +fi + +echo "Checking service availability..." + +# Check if docker-compose is running +if ! docker compose ps >/dev/null 2>&1; then + echo "Error: Docker services are not running. Start them with 'docker compose up -d'" + exit 1 +fi + +# Function to check HTTP service +check_http_service() { + local service=$1 + local url=$2 + echo -n "Checking $service at $url... " + + response=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null) + if [[ $response -ge 200 && $response -lt 500 ]]; then + echo "ACCESSIBLE ✅ (HTTP $response)" + else + echo "NOT ACCESSIBLE ❌ (HTTP $response)" + fi +} + +# Check n8n +check_http_service "n8n" "http://localhost:${N8N_PORT}/healthz" + +# Check temporal-ui +check_http_service "temporal-ui" "http://localhost:${TEMPORAL_UI_PORT}" + +# Check opensearch +check_http_service "opensearch" "http://localhost:${OPENSEARCH_PORT}" + +# Check temporal service +echo -n "Checking temporal at localhost:${TEMPORAL_PORT}... " +if nc -z localhost ${TEMPORAL_PORT} >/dev/null 2>&1; then + echo "ACCESSIBLE ✅" +else + echo "NOT ACCESSIBLE ❌" +fi + +# Check PostgreSQL +echo -n "Checking postgresql at localhost:${POSTGRES_PORT}... " +if docker compose exec postgresql pg_isready -h localhost -p ${POSTGRES_PORT} -U ${POSTGRES_USER} >/dev/null 2>&1; then + echo "ACCESSIBLE ✅" +else + echo "NOT ACCESSIBLE ❌" +fi + +echo -e "\nService URLs:" +echo "- n8n: http://localhost:${N8N_PORT}" +echo "- Temporal UI: http://localhost:${TEMPORAL_UI_PORT}" +echo "- OpenSearch: http://localhost:${OPENSEARCH_PORT}" \ No newline at end of file diff --git a/scripts/setup_volumes.sh b/scripts/setup_volumes.sh new file mode 100755 index 0000000..c5c2c84 --- /dev/null +++ b/scripts/setup_volumes.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Create volume directories for Docker Compose +echo "Setting up volume directories..." +mkdir -p volumes/opensearch-data +mkdir -p volumes/postgresql-data +mkdir -p volumes/n8n_data + +# Set broader permissions to ensure Docker can access these directories +echo "Setting permissions for Docker access..." +chmod -R 777 volumes/opensearch-data +chmod -R 777 volumes/postgresql-data +chmod -R 777 volumes/n8n_data + +echo "Volume directories created successfully with Docker-accessible permissions." \ No newline at end of file