From 5bdfbe6dee3ea527d832fa1910de9f2bf1ff44ee Mon Sep 17 00:00:00 2001 From: Adish M Date: Tue, 25 Feb 2025 12:35:17 +0530 Subject: [PATCH] Adding sub-modules and Ops changes --- .github/workflows/render-preview-deploy.yml | 735 ++++++++++++++++---- .gitmodules | 8 + docker/ee/ee-preview.Dockerfile | 123 ++++ docker/ee/ee-production.Dockerfile | 166 +++++ frontend/ee | 1 + server/ee | 1 + 6 files changed, 909 insertions(+), 125 deletions(-) create mode 100644 .gitmodules create mode 100644 docker/ee/ee-preview.Dockerfile create mode 100644 docker/ee/ee-production.Dockerfile create mode 160000 frontend/ee create mode 160000 server/ee diff --git a/.github/workflows/render-preview-deploy.yml b/.github/workflows/render-preview-deploy.yml index 9abca231dd..500317dd19 100644 --- a/.github/workflows/render-preview-deploy.yml +++ b/.github/workflows/render-preview-deploy.yml @@ -11,13 +11,16 @@ permissions: issues: write jobs: - create-review-app: - if: ${{ github.event.action == 'labeled' && github.event.label.name == 'create-review-app' }} + +# Community Edition + + create-ce-review-app: + if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'create-ce-review-app' || github.event.label.name == 'review-app') }} runs-on: ubuntu-latest steps: - - name: Create deployment - id: create-deployment + - name: Creating deployment for CE + id: create-ce-deployment run: | export RESPONSE=$(curl --request POST \ --url https://api.render.com/v1/services \ @@ -28,11 +31,11 @@ jobs: { "autoDeploy": "yes", "branch": "${{ env.BRANCH_NAME }}", - "name": "ToolJet PR #${{ env.PR_NUMBER }}", + "name": "ToolJet CE PR #${{ env.PR_NUMBER }}", "notifyOnFail": "default", "ownerId": "tea-caeo4bj19n072h3dddc0", - "repo": "${{ github.event.pull_request.head.repo.git_url }}", - "slug": "tooljet-pr-${{ env.PR_NUMBER }}", + "repo": "https://github.com/ToolJet/ToolJet", + "slug": "tooljet-ce-pr-${{ env.PR_NUMBER }}", "suspended": "not_suspended", "suspenders": [], "type": "web_service", @@ -55,11 +58,11 @@ jobs: }, { "key": "PG_DB", - "value": "${{ env.PR_NUMBER }}" + "value": "${{ env.PR_NUMBER }}-ce" }, { "key": "TOOLJET_DB", - "value": "${{ env.PR_NUMBER }}-tjdb" + "value": "${{ env.PR_NUMBER }}-ce-tjdb" }, { "key": "TOOLJET_DB_HOST", @@ -68,7 +71,7 @@ jobs: { "key": "TOOLJET_DB_USER", "value": "${{ secrets.RENDER_PG_USER }}" - }, + }, { "key": "TOOLJET_DB_PASS", "value": "${{ secrets.RENDER_PG_PASS }}" @@ -83,7 +86,7 @@ jobs: }, { "key": "PGRST_DB_URI", - "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-tjdb" + "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-ce-tjdb" }, { "key": "PGRST_HOST", @@ -103,7 +106,7 @@ jobs: }, { "key": "TOOLJET_HOST", - "value": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" + "value": "https://tooljet-ce-pr-${{ env.PR_NUMBER }}.onrender.com" }, { "key": "DISABLE_TOOLJET_TELEMETRY", @@ -129,6 +132,18 @@ jobs: "key": "SMTP_PASSWORD", "value": "${{ secrets.RENDER_SMTP_PASSWORD }}" }, + { + "key": "TEMPORAL_SERVER_ADDRESS", + "value": "https://auto-setup-1-25-1.onrender.com" + }, + { + "key": "TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS", + "value": "tooljet-ce-pr-${{ env.PR_NUMBER }}" + }, + { + "key": "TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE", + "value": "default" + }, { "key": "TOOLJET_MARKETPLACE_URL", "value": "${{ secrets.MARKETPLACE_BUCKET }}" @@ -140,7 +155,7 @@ jobs: "envSpecificDetails": { "dockerCommand": "", "dockerContext": "./", - "dockerfilePath": "./docker/preview.Dockerfile" + "dockerfilePath": "./docker/ce-preview.Dockerfile" }, "healthCheckPath": "/api/health", "numInstances": 1, @@ -151,7 +166,7 @@ jobs: "plan": "starter", "pullRequestPreviewsEnabled": "no", "region": "oregon", - "url": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" + "url": "https://tooljet-ce-pr-${{ env.PR_NUMBER }}.onrender.com" } }') @@ -168,7 +183,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'Deployment: https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/web/${{ env.SERVICE_ID }}' + body: 'Community Edition:- \n Deployment: https://tooljet-ce-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/web/${{ env.SERVICE_ID }}' }) - uses: actions/github-script@v6 @@ -179,7 +194,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - name: 'create-review-app' + name: 'create-ce-review-app' }) } catch (e) { console.log(e) @@ -189,11 +204,11 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - labels: ['active-review-app'] + labels: ['active-ce-review-app'] }) - destroy-review-app: - if: ${{ (github.event.action == 'labeled' && github.event.label.name == 'destroy-review-app') || github.event.action == 'closed' }} + destroy-ce-review-app: + if: ${{ (github.event.action == 'labeled' && github.event.label.name == 'destroy-ce-review-app') || github.event.action == 'closed' }} runs-on: ubuntu-latest steps: @@ -218,7 +233,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - name: 'destroy-review-app' + name: 'destroy-ce-review-app' }) } catch (e) { console.log(e) @@ -229,7 +244,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - name: 'suspend-review-app' + name: 'suspend-ce-review-app' }) } catch (e) { console.log(e) @@ -240,7 +255,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - name: 'active-review-app' + name: 'active-ce-review-app' }) } catch (e) { console.log(e) @@ -259,8 +274,8 @@ jobs: PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} PGPORT: 5432 PGUSER: ${{ secrets.RENDER_DS_PG_USER }} - PGDATABASE: ${{ env.PR_NUMBER }} - PGTJBDATABASE: ${{ env.PR_NUMBER }}-tjdb + PGDATABASE: ${{ env.PR_NUMBER }}-ce + PGTJBDATABASE: ${{ env.PR_NUMBER }}-ce-tjdb run: | if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then echo "Database $PGDATABASE exists, deleting..." @@ -276,8 +291,8 @@ jobs: echo "Database $PGTJBDATABASE does not exist." fi - suspend-review-app: - if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-review-app' }} + suspend-ce-review-app: + if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ce-review-app' }} runs-on: ubuntu-latest steps: @@ -302,14 +317,14 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - name: 'active-review-app' + name: 'active-ce-review-app' }) } catch (e) { console.log(e) } - resume-review-app: - if: ${{ github.event.action == 'unlabeled' && github.event.label.name == 'suspend-review-app' }} + resume-ce-review-app: + if: ${{ github.event.action == 'unlabeled' && github.event.label.name == 'suspend-ce-review-app' }} runs-on: ubuntu-latest steps: @@ -333,7 +348,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - labels: ['active-review-app'] + labels: ['active-ce-review-app'] }) try { @@ -341,98 +356,23 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - name: 'suspend-review-app' + name: 'suspend-ce-review-app' }) } catch (e) { console.log(e) } - recreate-review-app: - if: ${{ github.event.action == 'labeled' && github.event.label.name == 'recreate-review-app' }} + + +# Enterprise Edition + + create-ee-review-app: + if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'create-ee-review-app' || github.event.label.name == 'review-app') }} runs-on: ubuntu-latest steps: - - name: Delete service - run: | - export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ - --header 'accept: application/json' \ - --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ - jq -r '.[0].service.id') - - curl --request DELETE \ - --url https://api.render.com/v1/services/$SERVICE_ID \ - --header 'accept: application/json' \ - --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' - - - uses: actions/github-script@v6 - with: - script: | - try { - await github.rest.issues.removeLabel({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - name: 'destroy-review-app' - }) - } catch (e) { - console.log(e) - } - - try { - await github.rest.issues.removeLabel({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - name: 'suspend-review-app' - }) - } catch (e) { - console.log(e) - } - - try { - await github.rest.issues.removeLabel({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - name: 'active-review-app' - }) - } catch (e) { - console.log(e) - } - - - name: Install PostgreSQL client - run: | - sudo apt update - sudo apt install postgresql-client -y - - - name: Wait after installing PostgreSQL - run: sleep 25 - - - name: Drop PostgreSQL PR databases - env: - PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} - PGPORT: 5432 - PGUSER: ${{ secrets.RENDER_DS_PG_USER }} - PGDATABASE: ${{ env.PR_NUMBER }} - PGTJBDATABASE: ${{ env.PR_NUMBER }}-tjdb - run: | - if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then - echo "Database $PGDATABASE exists, deleting..." - PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" - else - echo "Database $PGDATABASE does not exist." - fi - - if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then - echo "Database $PGTJBDATABASE exists, deleting..." - PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" - else - echo "Database $PGTJBDATABASE does not exist." - fi - - - name: Create deployment - id: create-deployment + - name: Creating deployment for Enterprise Edition + id: create-ee-deployment run: | export RESPONSE=$(curl --request POST \ --url https://api.render.com/v1/services \ @@ -446,8 +386,8 @@ jobs: "name": "ToolJet PR #${{ env.PR_NUMBER }}", "notifyOnFail": "default", "ownerId": "tea-caeo4bj19n072h3dddc0", - "repo": "https://${{ secrets.RENDER_GITHUB_PAT }}@github.com/ToolJet/tj-ee", - "slug": "tooljet-pr-${{ env.PR_NUMBER }}", + "repo": "https://github.com/ToolJet/ToolJet", + "slug": "tooljet-ee-pr-${{ env.PR_NUMBER }}", "suspended": "not_suspended", "suspenders": [], "type": "web_service", @@ -470,11 +410,11 @@ jobs: }, { "key": "PG_DB", - "value": "${{ env.PR_NUMBER }}" + "value": "${{ env.PR_NUMBER }}-ee" }, { "key": "TOOLJET_DB", - "value": "${{ env.PR_NUMBER }}-tjdb" + "value": "${{ env.PR_NUMBER }}-ee-tjdb" }, { "key": "TOOLJET_DB_HOST", @@ -498,7 +438,7 @@ jobs: }, { "key": "PGRST_DB_URI", - "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-tjdb" + "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-ee-tjdb" }, { "key": "PGRST_HOST", @@ -518,7 +458,7 @@ jobs: }, { "key": "TOOLJET_HOST", - "value": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" + "value": "https://tooljet-ee-pr-${{ env.PR_NUMBER }}.onrender.com" }, { "key": "DISABLE_TOOLJET_TELEMETRY", @@ -556,9 +496,29 @@ jobs: "key": "LICENSE_KEY", "value": "${{ secrets.RENDER_LICENSE_KEY }}" }, + { + "key": "TEMPORAL_SERVER_ADDRESS", + "value": "https://auto-setup-1-25-1.onrender.com" + }, + { + "key": "TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS", + "value": "tooljet-ee-pr-${{ env.PR_NUMBER }}" + }, + { + "key": "TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE", + "value": "default" + }, { "key": "TOOLJET_MARKETPLACE_URL", "value": "${{ secrets.MARKETPLACE_BUCKET }}" + }, + { + "key": "BRANCH_NAME", + "value": "${{ env.BRANCH_NAME }}" + }, + { + "key": "CUSTOM_GITHUB_TOKEN", + "value": "${{ secrets.CUSTOM_GITHUB_TOKEN }}" } ], "serviceDetails": { @@ -567,7 +527,7 @@ jobs: "envSpecificDetails": { "dockerCommand": "", "dockerContext": "./", - "dockerfilePath": "./docker/preview.Dockerfile" + "dockerfilePath": "./docker/ee/ee-preview.Dockerfile" }, "healthCheckPath": "/api/health", "numInstances": 1, @@ -578,7 +538,7 @@ jobs: "plan": "starter", "pullRequestPreviewsEnabled": "no", "region": "oregon", - "url": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" + "url": "https://tooljet-ee-pr-${{ env.PR_NUMBER }}.onrender.com" } }') @@ -595,7 +555,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'Deployment: https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/web/${{ env.SERVICE_ID }}' + body: 'Enterpise Edition: \n Deployment: https://tooljet-ee-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/web/${{ env.SERVICE_ID }}' }) - uses: actions/github-script@v6 @@ -606,7 +566,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - name: 'create-review-app' + name: 'create-ee-review-app' }) } catch (e) { console.log(e) @@ -616,5 +576,530 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - labels: ['active-review-app'] + labels: ['active-ee-review-app'] }) + + destroy-ee-review-app: + if: ${{ (github.event.action == 'labeled' && github.event.label.name == 'destroy-ee-review-app') || github.event.action == 'closed' }} + runs-on: ubuntu-latest + + steps: + - name: Delete service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request DELETE \ + --url https://api.render.com/v1/services/$SERVICE_ID \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'destroy-ee-review-app' + }) + } catch (e) { + console.log(e) + } + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'suspend-ee-review-app' + }) + } catch (e) { + console.log(e) + } + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'active-ee-review-app' + }) + } catch (e) { + console.log(e) + } + + - name: Install PostgreSQL client + run: | + sudo apt update + sudo apt install postgresql-client -y + + - name: Wait after installing PostgreSQL + run: sleep 25 + + - name: Drop PostgreSQL PR databases + env: + PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} + PGPORT: 5432 + PGUSER: ${{ secrets.RENDER_DS_PG_USER }} + PGDATABASE: ${{ env.PR_NUMBER }}-ee + PGTJBDATABASE: ${{ env.PR_NUMBER }}-ee-tjdb + run: | + if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then + echo "Database $PGDATABASE exists, deleting..." + PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" + else + echo "Database $PGDATABASE does not exist." + fi + + if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then + echo "Database $PGTJBDATABASE exists, deleting..." + PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" + else + echo "Database $PGTJBDATABASE does not exist." + fi + + suspend-ee-review-app: + if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ee-review-app' }} + runs-on: ubuntu-latest + + steps: + - name: Suspend service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request POST \ + --url https://api.render.com/v1/services/$SERVICE_ID/suspend \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'active-ee-review-app' + }) + } catch (e) { + console.log(e) + } + + resume-ee-review-app: + if: ${{ github.event.action == 'unlabeled' && github.event.label.name == 'suspend-ee-review-app' }} + runs-on: ubuntu-latest + + steps: + - name: Resume service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request POST \ + --url https://api.render.com/v1/services/$SERVICE_ID/resume \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + await github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['active-ee-review-app'] + }) + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'suspend-ee-review-app' + }) + } catch (e) { + console.log(e) + } + + + + +# Cloud Edition + + create-cloud-review-app: + if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'create-cloud-review-app' || github.event.label.name == 'review-app') }} + runs-on: ubuntu-latest + + steps: + - name: Creating deployment for Cloud Edition + id: create-cloud-deployment + run: | + export RESPONSE=$(curl --request POST \ + --url https://api.render.com/v1/services \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --header 'Authorization: Bearer ${{ secrets.RENDER_API_KEY }}' \ + --data ' + { + "autoDeploy": "yes", + "branch": "${{ env.BRANCH_NAME }}", + "name": "ToolJet PR #${{ env.PR_NUMBER }}", + "notifyOnFail": "default", + "ownerId": "tea-caeo4bj19n072h3dddc0", + "repo": "https://github.com/ToolJet/ToolJet", + "slug": "tooljet-cloud-pr-${{ env.PR_NUMBER }}", + "suspended": "not_suspended", + "suspenders": [], + "type": "web_service", + "envVars": [ + { + "key": "PG_HOST", + "value": "${{ secrets.RENDER_PG_HOST }}" + }, + { + "key": "PG_PORT", + "value": "5432" + }, + { + "key": "PG_USER", + "value": "${{ secrets.RENDER_PG_USER }}" + }, + { + "key": "PG_PASS", + "value": "${{ secrets.RENDER_PG_PASS }}" + }, + { + "key": "PG_DB", + "value": "${{ env.PR_NUMBER }}-cloud" + }, + { + "key": "TOOLJET_DB", + "value": "${{ env.PR_NUMBER }}-cloud-tjdb" + }, + { + "key": "TOOLJET_DB_HOST", + "value": "${{ secrets.RENDER_PG_HOST }}" + }, + { + "key": "TOOLJET_DB_USER", + "value": "${{ secrets.RENDER_PG_USER }}" + }, + { + "key": "TOOLJET_DB_PASS", + "value": "${{ secrets.RENDER_PG_PASS }}" + }, + { + "key": "TOOLJET_DB_PORT", + "value": "5432" + }, + { + "key": "PGRST_DB_PRE_CONFIG", + "value": "postgrest.pre_config" + }, + { + "key": "PGRST_DB_URI", + "value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-cloud-tjdb" + }, + { + "key": "PGRST_HOST", + "value": "127.0.0.1:3000" + }, + { + "key": "PGRST_JWT_SECRET", + "value": "r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" + }, + { + "key": "PGRST_LOG_LEVEL", + "value": "info" + }, + { + "key": "PORT", + "value": "80" + }, + { + "key": "TOOLJET_HOST", + "value": "https://tooljet-cloud-pr-${{ env.PR_NUMBER }}.onrender.com" + }, + { + "key": "DISABLE_TOOLJET_TELEMETRY", + "value": "true" + }, + { + "key": "SMTP_ADDRESS", + "value": "smtp.mailtrap.io" + }, + { + "key": "SMTP_DOMAIN", + "value": "smtp.mailtrap.io" + }, + { + "key": "SMTP_PORT", + "value": "2525" + }, + { + "key": "SMTP_USERNAME", + "value": "${{ secrets.RENDER_SMTP_USERNAME }}" + }, + { + "key": "SMTP_PASSWORD", + "value": "${{ secrets.RENDER_SMTP_PASSWORD }}" + }, + { + "key": "REDIS_HOST", + "value": "${{ secrets.RENDER_REDIS_HOST }}" + }, + { + "key": "REDIS_PORT", + "value": "${{ secrets.RENDER_REDIS_PORT }}" + }, + { + "key": "LICENSE_KEY", + "value": "${{ secrets.RENDER_LICENSE_KEY }}" + }, + { + "key": "TEMPORAL_SERVER_ADDRESS", + "value": "https://auto-setup-1-25-1.onrender.com" + }, + { + "key": "TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS", + "value": "tooljet-cloud-pr-${{ env.PR_NUMBER }}" + }, + { + "key": "TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE", + "value": "default" + }, + { + "key": "TOOLJET_MARKETPLACE_URL", + "value": "${{ secrets.MARKETPLACE_BUCKET }}" + }, + { + "key": "CUSTOM_GITHUB_TOKEN", + "value": "${{ secrets.CUSTOM_GITHUB_TOKEN }}" + } + ], + "serviceDetails": { + "disk": null, + "env": "docker", + "envSpecificDetails": { + "dockerCommand": "", + "dockerContext": "./", + "dockerfilePath": "./docker/cloud/cloud-preview.Dockerfile" + }, + "healthCheckPath": "/api/health", + "numInstances": 1, + "openPorts": [{ + "port": 80, + "protocol": "TCP" + }], + "plan": "starter", + "pullRequestPreviewsEnabled": "no", + "region": "oregon", + "url": "https://tooljet-cloud-pr-${{ env.PR_NUMBER }}.onrender.com" + } + }') + + echo "response: $RESPONSE" + export SERVICE_ID=$(echo $RESPONSE | jq -r '.service.id') + echo "SERVICE_ID=$SERVICE_ID" >> $GITHUB_ENV + + - name: Comment deployment URL + uses: actions/github-script@v5 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'Cloud Edition: \n Deployment: https://tooljet-cloud-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/web/${{ env.SERVICE_ID }}' + }) + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'create-cloud-review-app' + }) + } catch (e) { + console.log(e) + } + + await github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['active-cloud-review-app'] + }) + + destroy-cloud-review-app: + if: ${{ (github.event.action == 'labeled' && github.event.label.name == 'destroy-cloud-review-app') || github.event.action == 'closed' }} + runs-on: ubuntu-latest + + steps: + - name: Delete service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request DELETE \ + --url https://api.render.com/v1/services/$SERVICE_ID \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'destroy-cloud-review-app' + }) + } catch (e) { + console.log(e) + } + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'suspend-cloud-review-app' + }) + } catch (e) { + console.log(e) + } + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'active-cloud-review-app' + }) + } catch (e) { + console.log(e) + } + + - name: Install PostgreSQL client + run: | + sudo apt update + sudo apt install postgresql-client -y + + - name: Wait after installing PostgreSQL + run: sleep 25 + + - name: Drop PostgreSQL PR databases + env: + PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} + PGPORT: 5432 + PGUSER: ${{ secrets.RENDER_DS_PG_USER }} + PGDATABASE: ${{ env.PR_NUMBER }}-cloud + PGTJBDATABASE: ${{ env.PR_NUMBER }}-cloud-tjdb + run: | + if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then + echo "Database $PGDATABASE exists, deleting..." + PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" + else + echo "Database $PGDATABASE does not exist." + fi + + if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then + echo "Database $PGTJBDATABASE exists, deleting..." + PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" + else + echo "Database $PGTJBDATABASE does not exist." + fi + + suspend-cloud-review-app: + if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-cloud-review-app' }} + runs-on: ubuntu-latest + + steps: + - name: Suspend service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request POST \ + --url https://api.render.com/v1/services/$SERVICE_ID/suspend \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'active-cloud-review-app' + }) + } catch (e) { + console.log(e) + } + + resume-cloud-review-app: + if: ${{ github.event.action == 'unlabeled' && github.event.label.name == 'suspend-cloud-review-app' }} + runs-on: ubuntu-latest + + steps: + - name: Resume service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request POST \ + --url https://api.render.com/v1/services/$SERVICE_ID/resume \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + await github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['active-cloud-review-app'] + }) + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'suspend-cloud-review-app' + }) + } catch (e) { + console.log(e) + } + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..428ef18262 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +[submodule "frontend/ee"] + path = frontend/ee + url = https://github.com/ToolJet/ee-frontend.git + branch = modularisation/v3 +[submodule "server/ee"] + path = server/ee + url = https://github.com/ToolJet/ee-server.git + branch = modularisation/v3 diff --git a/docker/ee/ee-preview.Dockerfile b/docker/ee/ee-preview.Dockerfile new file mode 100644 index 0000000000..309f234ea8 --- /dev/null +++ b/docker/ee/ee-preview.Dockerfile @@ -0,0 +1,123 @@ +FROM node:18.18.2-buster AS builder +# Fix for JS heap limit allocation issue +ENV NODE_OPTIONS="--max-old-space-size=9096" + +RUN mkdir -p /app + +WORKDIR /app + +ARG CUSTOM_GITHUB_TOKEN +ARG BRANCH_NAME=modularisation/v3 + +# Clone and checkout the frontend repository +RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" + +RUN git config --global http.version HTTP/1.1 +RUN git config --global http.postBuffer 524288000 +RUN git clone https://github.com/ToolJet/ToolJet.git . + +# The branch name needs to be changed the branch with modularisation in CE repo +RUN git checkout modularisation/v3 + + +RUN git submodule update --init --recursive + +# Checkout the same branch in submodules if it exists, otherwise stay on default branch +RUN git submodule foreach 'git checkout ${BRANCH_NAME} || true' + +# Scripts for building +COPY ./package.json ./package.json + +# Build plugins +COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/ +RUN npm --prefix plugins install +COPY ./plugins/ ./plugins/ +RUN NODE_ENV=production npm --prefix plugins run build +RUN npm --prefix plugins prune --production + +ENV EDITION=ee + +# Build frontend +COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/ +RUN npm --prefix frontend install +COPY ./frontend/ ./frontend/ +RUN npm --prefix frontend run build --production +RUN npm --prefix frontend prune --production + +ENV NODE_ENV=production +ENV EDITION=ee + +# Build server +COPY ./server/package.json ./server/package-lock.json ./server/ +RUN npm --prefix server install +COPY ./server/ ./server/ +RUN npm install -g @nestjs/cli +RUN npm --prefix server run build + +FROM node:18.18.2-bullseye + +RUN apt-get update -yq \ + && apt-get install curl gnupg zip -yq \ + && apt-get install -yq build-essential \ + && apt-get clean -y + +# copy postgrest executable +COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin + +ENV NODE_ENV=production +ENV EDITION=ee +ENV NODE_OPTIONS="--max-old-space-size=4096" +RUN apt-get update && apt-get install -y postgresql-client freetds-dev libaio1 wget supervisor + +# Install Instantclient Basic Light Oracle and Dependencies +WORKDIR /opt/oracle +RUN wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linuxx64.zip && \ + wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + unzip instantclient-basiclite-linuxx64.zip && rm -f instantclient-basiclite-linuxx64.zip && \ + unzip instantclient-basiclite-linux.x64-11.2.0.4.0.zip && rm -f instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + cd /opt/oracle/instantclient_21_10 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + cd /opt/oracle/instantclient_11_2 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig +# Set the Instant Client library paths +ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}" + +WORKDIR / + +RUN mkdir -p /app /var/log/supervisor +COPY /deploy/docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +# copy npm scripts +COPY --from=builder /app/package.json ./app/package.json +# copy plugins dependencies +COPY --from=builder /app/plugins/dist ./app/plugins/dist +COPY --from=builder /app/plugins/client.js ./app/plugins/client.js +COPY --from=builder /app/plugins/node_modules ./app/plugins/node_modules +COPY --from=builder /app/plugins/packages/common ./app/plugins/packages/common +COPY --from=builder /app/plugins/package.json ./app/plugins/package.json +# copy frontend build +COPY --from=builder /app/frontend/build ./app/frontend/build +# copy server build +COPY --from=builder /app/server/package.json ./app/server/package.json +COPY --from=builder /app/server/.version ./app/server/.version +COPY --from=builder /app/server/ee/keys ./app/server/ee/keys +COPY --from=builder /app/server/entrypoint.sh ./app/server/entrypoint.sh +COPY --from=builder /app/server/node_modules ./app/server/node_modules +COPY --from=builder /app/server/templates ./app/server/templates +COPY --from=builder /app/server/scripts ./app/server/scripts +COPY --from=builder /app/server/dist ./app/server/dist + +WORKDIR /app + +# ENV defaults +ENV TOOLJET_HOST=http://localhost:80 \ + PGRST_HOST=http://localhost:3000 \ + PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \ + TOOLJET_DB=tooljet_db \ + ENABLE_TOOLJET_DB=true \ + PORT=80 \ + LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \ + SECRET_KEY_BASE=replace_with_secret_key_base \ + ORM_LOGGING=all \ + TERM=xterm + +CMD ["/usr/bin/supervisord"] \ No newline at end of file diff --git a/docker/ee/ee-production.Dockerfile b/docker/ee/ee-production.Dockerfile new file mode 100644 index 0000000000..5f113205df --- /dev/null +++ b/docker/ee/ee-production.Dockerfile @@ -0,0 +1,166 @@ +FROM node:18.18.2-buster AS builder + +# Fix for JS heap limit allocation issue +ENV NODE_OPTIONS="--max-old-space-size=9096" + +RUN npm i -g npm@9.8.1 +RUN mkdir -p /app +# RUN npm cache clean --force + +WORKDIR /app + +# Set GitHub token and branch as build arguments +ARG CUSTOM_GITHUB_TOKEN +ARG BRANCH_NAME=modularisation/v3 + +# Clone and checkout the frontend repository +RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" + +RUN git config --global http.version HTTP/1.1 +RUN git config --global http.postBuffer 524288000 +RUN git clone https://github.com/ToolJet/ToolJet.git . + +# The branch name needs to be changed the branch with modularisation in CE repo +RUN git checkout modularisation/v3 + +RUN git submodule update --init --recursive + +# Checkout the same branch in submodules if it exists, otherwise stay on default branch +RUN git submodule foreach 'git checkout ${BRANCH_NAME} || true' + +# Scripts for building +COPY ./package.json ./package.json + +# Build plugins +COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/ +RUN npm --prefix plugins install +COPY ./plugins/ ./plugins/ +RUN NODE_ENV=production npm --prefix plugins run build +RUN npm --prefix plugins prune --production + +ENV EDITION=ee + +# Build frontend +COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/ +RUN npm --prefix frontend install +COPY ./frontend/ ./frontend/ +RUN npm --prefix frontend run build --production +RUN npm --prefix frontend prune --production + +ENV NODE_ENV=production +ENV EDITION=ee + +# Build server +COPY ./server/package.json ./server/package-lock.json ./server/ +RUN npm --prefix server install +COPY ./server/ ./server/ +RUN npm install -g @nestjs/cli +RUN npm --prefix server run build + +FROM debian:11 + +RUN apt-get update -yq \ + && apt-get install curl gnupg zip -yq \ + && apt-get install -yq build-essential \ + && apt-get clean -y + + +RUN curl -O https://nodejs.org/dist/v18.18.2/node-v18.18.2-linux-x64.tar.xz \ + && tar -xf node-v18.18.2-linux-x64.tar.xz \ + && mv node-v18.18.2-linux-x64 /usr/local/lib/nodejs \ + && echo 'export PATH="/usr/local/lib/nodejs/bin:$PATH"' >> /etc/profile.d/nodejs.sh \ + && /bin/bash -c "source /etc/profile.d/nodejs.sh" \ + && rm node-v18.18.2-linux-x64.tar.xz +ENV PATH=/usr/local/lib/nodejs/bin:$PATH + +ENV NODE_ENV=production +ENV EDITION=ee +ENV NODE_OPTIONS="--max-old-space-size=4096" +RUN apt-get update && \ + apt-get install -y postgresql-client freetds-dev libaio1 wget && \ + apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y --force-yes && \ + apt-get -y autoremove && \ + apt-get -y autoclean + +# Install Instantclient Basic Light Oracle and Dependencies +WORKDIR /opt/oracle + +RUN wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linuxx64.zip && \ + wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + unzip instantclient-basiclite-linuxx64.zip && rm -f instantclient-basiclite-linuxx64.zip && \ + unzip instantclient-basiclite-linux.x64-11.2.0.4.0.zip && rm -f instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + cd /opt/oracle/instantclient_21_10 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + cd /opt/oracle/instantclient_11_2 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig +# Set the Instant Client library paths +ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}" + + +WORKDIR / + +RUN mkdir -p /app +# copy npm scripts +COPY --from=builder /app/package.json ./app/package.json +# copy plugins dependencies +COPY --from=builder /app/plugins/dist ./app/plugins/dist +COPY --from=builder /app/plugins/client.js ./app/plugins/client.js +COPY --from=builder /app/plugins/node_modules ./app/plugins/node_modules +COPY --from=builder /app/plugins/packages/common ./app/plugins/packages/common +COPY --from=builder /app/plugins/package.json ./app/plugins/package.json +# copy frontend build +COPY --from=builder /app/frontend/build ./app/frontend/build +# copy server build +COPY --from=builder /app/server/package.json ./app/server/package.json +COPY --from=builder /app/server/.version ./app/server/.version +COPY --from=builder /app/server/ee/keys ./app/server/ee/keys +COPY --from=builder /app/server/entrypoint.sh ./app/server/entrypoint.sh +COPY --from=builder /app/server/node_modules ./app/server/node_modules +COPY --from=builder /app/server/templates ./app/server/templates +COPY --from=builder /app/server/scripts ./app/server/scripts +COPY --from=builder /app/server/dist ./app/server/dist + +# Define non-sudo user +RUN useradd --create-home --home-dir /home/appuser appuser \ + && chown -R appuser:0 /app \ + && chown -R appuser:0 /home \ + && chmod u+x /app \ + && chmod u+x /home \ + && chmod -R g=u /app \ + && chmod -R g=u /home + +# Create directory /home/appuser and set ownership to appuser (Refer doc for understanding the changes https://app.clickup.com/37484951/v/dc/13qycq-4081) +RUN mkdir -p /home/appuser \ + && chown -R appuser:0 /home/appuser \ + && chmod g+s /home/appuser \ + && chmod -R g=u /home/appuser \ + && npm cache clean --force + +# Create directory /tmp/.npm/npm-cache/ and set ownership to appuser (Refer doc for understanding the changes https://app.clickup.com/37484951/v/dc/13qycq-4081) +RUN mkdir -p /tmp/.npm/npm-cache/ \ + && chown -R appuser:0 /tmp/.npm/npm-cache/ \ + && chmod g+s /tmp/.npm/npm-cache/ \ + && chmod -R g=u /tmp/.npm/npm-cache \ + && npm cache clean --force + +# Set npm cache directory globally +RUN npm config set cache /tmp/.npm/npm-cache/ --global +ENV npm_config_cache /tmp/.npm/npm-cache/ + +# Create directory /tmp/.npm/npm-cache/_logs and set ownership to appuser +RUN mkdir -p /tmp/.npm/npm-cache/_logs \ + && chown -R appuser:0 /tmp/.npm/npm-cache/_logs \ + && chmod g+s /tmp/.npm/npm-cache/_logs \ + && chmod -R g=u /tmp/.npm/npm-cache/_logs + +ENV HOME=/home/appuser + +# Switch back to appuser +USER appuser + +WORKDIR /app +# Dependencies for scripts outside nestjs +RUN npm install dotenv@10.0.0 joi@17.4.1 + +RUN npm cache clean --force + +ENTRYPOINT ["./server/entrypoint.sh"] \ No newline at end of file diff --git a/frontend/ee b/frontend/ee new file mode 160000 index 0000000000..48b34618a9 --- /dev/null +++ b/frontend/ee @@ -0,0 +1 @@ +Subproject commit 48b34618a96d70a64fa6579a54da941e457899d0 diff --git a/server/ee b/server/ee new file mode 160000 index 0000000000..88dcd4e30b --- /dev/null +++ b/server/ee @@ -0,0 +1 @@ +Subproject commit 88dcd4e30b5e78df1b873189c8416dd26edcaa1c