diff --git a/deploy/ansible/README.MD b/deploy/ansible/README.MD new file mode 100644 index 0000000000..1fffe5ddd0 --- /dev/null +++ b/deploy/ansible/README.MD @@ -0,0 +1,113 @@ +# Introduction +This document will explain, to those unfamiliar with Ansible, how they can get an Ansible environment set-up quickly, with the end goal of deploying Appsmith. +It is a quick, dirty HowTo format, not intended to teach you Ansible's full capabilities. Ansible is an incredible tool, with great documentation, a welcoming community, and it's all very easy to pick up - not to mention extremely powerful and suited for just about any situation. + +# Operational Overview +Ansible works on a "push to clients" basis. You have your control node, which pushes all the configuration/ad-hoc tasks out to your systems via SSH, with no client running on the systems you're deploying to! This model means it's very fast, efficient, secure, scalable, and extremely portable. +So, to control remote systems, you only need to install Ansible on your control node - your own desktop would make a great control node to deploy from + +# Getting Ansible +It's recommended that you check out Ansible's official documentation on installing (it's really easy!), but here's a quick rundown of installation methods: +## Package manager +If you're running a UNIX-like system, like Linux or BSD, Ansible is likely available in your official package repositories. Use your package manager to see if it's available, and if so, install it! Ansible's installation documentation has a section on this - just scroll down until you see your OS. +## Via Pip +Ansible is written in Python, so, it's only natural that it be available for install via pip. If you have pip installed, it's as easy as: +``` +$ sudo pip install ansible +``` +If not, check to see if you can install pip via your system's package manager (you want the Python 2.7 version!). +Or, if you're on Mac OS X, and you're not using Homebrew or pkgsrc, you should be able to install pip using easy_install, like so: +``` +$ sudo easy_install pip +``` +then +``` +$ sudo pip install ansible +``` + +# Simple Deployment Environment for Appsmith +So, now you've got Ansible installed, you can get ready to deploy Appsmith! + +## Prerequisites +- You must have SSH access to the system you want to deploy to as the root user. + +## Inventory set-up +First you will need to clone the appsmith repository to your machine & move to the ansible playbook folder +``` +$ git clone https://github.com/appsmithorg/appsmith.git +$ cd ./appsmith/ansible/appsmith_playbook +``` + +Make the inventory file `inventory`, for simplicity's sake: +``` +$ touch inventory +``` + +Now, with your editor, open the file and add the hostname or FQDN of the server(s) you want to deploy Appsmith to with the following pattern: +``` +appsmith ansible_host={{ SERVER_HOST }} ansible_port={{ SERVER_PORT }} ansible_user={{ SERVER_USER }} +``` + +If you are using SSH keypairs for authenticating your SSH connections to your server. You can tell Ansible your ssh private key file in the `inventory` file +using `ansible_ssh_private_key_file` +``` +appsmith ansible_host={{ SERVER_HOST }} ansible_port={{ SERVER_PORT }} ansible_user={{ SERVER_USER }} ansible_ssh_private_key_file={{ SSH_PRIVATE_KEY_FILE }} +``` + +After you completed the above step then we're pretty much done with the inventory + + +## Setup your configuration vars for Appsmith +The next step is to setup necessary configuration for your app to run such as environment variable, domain name, etc. + +First you need to open `appsmith-vars.yml` file with your editor. +There are some variables that will need input from you to get the application start correctly + +- `install_dir`: The absolute path of your app's installation folder on the server (required) +- `mongo_host`: Your mongo hostname. By default it will be `mongo` (required) +- `mongo_root_user`: Your mongo root user (required) +- `mongo_root_password`: Your mongo root password (required) +- `mongo_database`: Your mongo database name. By default, it will be `appsmith` (required) +- `user_encryption_password`: Encryption password to encrypt all credentials in the database (required) +- `user_encryption_salt`: Encryption salt used to encrypt all credentials in the database (required) +- `custom_domain`: Your custom domain for your app. Make sure that you have custom domain record map to your app's server (optional) + +Once you complete setup config vars for your app then we are ready to deploy our app on your server. + + +## Setup SSL (Optional) +This section will help you setup SSL for your custom domain of your app + +### Prerequisites +- You need to have a custom domain record map to your app's server + +Before running your playbook, open `appsmith-vars.yml` with your editor & edit the variables below: +- `ssl_enable`: Set this variable to `true` +- `letsencrypt_email`: Provide your email if you want to receive expiry notices when your certificate is coming up for renewal +- `is_ssl_staging`: Set this variable to `true` if you want to use certificate provided by let's encrypt staging environment + +## Run the Ansible playbook +After complete the above step. Now the only remain step we need to do is run the ansible playbook. +You can run the ansible playbook with the following command + +``` +$ ansible-playbook -i inventory main.yml --extra-var "@appsmith-vars.yml" +``` + +The command above will use the host information from the `inventory` file & feed your configuration vars from `appsmith-vars.yml` before running the playbook + +When it's all done, provided all went well and no parameters were changed, you should be able to visit your app on browser using your `custom_domain` or by your `SERVER_HOST` (if you didn't provide value for `custom_domain` variable ) + +**Note**: You can put your `inventory` file in other folder and then specify its path with the `-i` flag, for detail, check [Ansible Inventory documentation](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html) + + + + + + + + + + + + diff --git a/deploy/ansible/appsmith_playbook/appsmith-vars.yml b/deploy/ansible/appsmith_playbook/appsmith-vars.yml new file mode 100644 index 0000000000..81dd498371 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/appsmith-vars.yml @@ -0,0 +1,42 @@ +--- +user_email: 'youremail@gmail.com' +install_dir: '' +mongo_host: 'mongo' +mongo_root_user: '' +mongo_root_password: '' +mongo_database: 'appsmith' +user_encryption_password: '' +user_encryption_salt: '' +custom_domain: '' +ssl_enable: 'false' +letsencrypt_email: 'youremail@gmail.com' +is_ssl_staging: 'false' +disable_telemetry: 'true' +mail_enabled: 'false' +mail_from: '' +mail_to: '' +mail_host: '' +mail_port: '' +mail_ssl_enabled: 'false' +mail_username: '' +mail_password: '' +mail_auth: '' +google_client_id: '' +google_secret_id: '' +github_client_id: '' +github_secret_id: '' +google_maps_api_key: '' +sentry_dns: '' +smart_look_id: '' +marketplace_enabled: 'false' +segment_key: '' +optimizely_key: '' +algolia_api_id: '' +algolia_search_index_name: '' +algolia_api_key: '' +client_log_level: '' +tnc_pp: '' +version_id: '' +version_release_date: '' +intercom_app_id: '' + diff --git a/deploy/ansible/appsmith_playbook/main.yml b/deploy/ansible/appsmith_playbook/main.yml new file mode 100644 index 0000000000..021ec89309 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/main.yml @@ -0,0 +1,60 @@ +--- + - name: Configure the self-hosted server + hosts: appsmith + any_errors_fatal: true + vars: + analytics_webhook_uri: https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa + ipify_url: https://api64.ipify.org + + tasks: + - name: Deploy appsmith on host + block: + - name: Get OS + shell: cat /etc/*-release | awk -F= '$1 == "NAME" { gsub(/"/, ""); print $2; exit }' + register: os + + - name: Get app installation id + uri: + url: "{{ ipify_url }}" + return_content: yes + register: app_installation_id + + - name: Push installation started event to integromat + uri: + url: "{{ analytics_webhook_uri }}" + method: POST + body: "{\"userId\":\"{{ app_installation_id.content }}\",\"event\":\"Installation Started\",\"data\":{\"os\":\"{{ os.stdout }}\", \"platform\": \"ansible\"}}" + body_format: json + return_content: yes + + - name: Setup dependencies + import_role: + name: base + + - name: Generate config template + import_role: + name: generate_template + + - name: Run App + import_role: + name: start_app + + - name: Config SSL + import_role: + name: domain_ssl + - name: Push installation success event to integromat + uri: + url: "{{ analytics_webhook_uri }}" + method: POST + body: "{\"userId\":\"{{ app_installation_id.content }}\",\"event\":\"Installation Success\",\"data\":{\"os\":\"{{ os.stdout }}\", \"platform\": \"ansible\"}}" + body_format: json + return_content: yes + rescue: + - name: Push installation failed event to integromat + uri: + url: "{{ analytics_webhook_uri }}" + method: POST + body: "{\"userId\":\"{{ app_installation_id.content }}\",\"event\":\"Installation Support\",\"data\":{\"os\":\"{{ os.stdout }}\", \"platform\": \"ansible\", \"email\": \"{{ user_email }}\"}}" + body_format: json + return_content: yes + diff --git a/deploy/ansible/appsmith_playbook/roles/base/defaults/main.yml b/deploy/ansible/appsmith_playbook/roles/base/defaults/main.yml new file mode 100644 index 0000000000..6f4a08af2b --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/base/defaults/main.yml @@ -0,0 +1,25 @@ +--- +# Edition can be one of: 'ce' (Community Edition) or 'ee' (Enterprise Edition). +docker_edition: 'ce' +docker_package: "docker-{{ docker_edition }}" +docker_package_state: present + +# Service options. +docker_service_state: started +docker_service_enabled: true +docker_restart_handler_state: restarted + +# Docker Compose options. +docker_install_compose: true +docker_compose_version: "1.27.0" +docker_compose_path: /usr/local/bin/docker-compose + +# Used only for Debian/Ubuntu. Switch 'stable' to 'edge' if needed. +docker_apt_release_channel: stable +docker_apt_arch: amd64 +docker_apt_repository: "deb [arch={{ docker_apt_arch }}] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} {{ docker_apt_release_channel }}" +docker_apt_ignore_key_error: true +docker_apt_gpg_key: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg + +# A list of users who will be added to the docker group. +docker_users: [] diff --git a/deploy/ansible/appsmith_playbook/roles/base/tasks/install-docker.yml b/deploy/ansible/appsmith_playbook/roles/base/tasks/install-docker.yml new file mode 100644 index 0000000000..09b2b25aff --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/base/tasks/install-docker.yml @@ -0,0 +1,50 @@ +--- +- include_tasks: setup-ubuntu.yml + when: ansible_os_family == 'Debian' + +- name: Install Docker. + package: + name: "{{ docker_package }}" + state: "{{ docker_package_state }}" + become: yes + +- name: Ensure Docker is started and enabled at boot. + service: + name: docker + state: "{{ docker_service_state }}" + enabled: "{{ docker_service_enabled }}" + +- name: Ensure handlers are notified now to avoid firewall conflicts. + meta: flush_handlers + +- name: Ensure docker users are added to the docker group. + user: + name: "{{ ansible_user }}" + groups: docker + append: true + become: yes + +- name: reset ssh connection to allow user changes to affect 'current login user' + meta: reset_connection + +- name: Check current docker-compose version. + command: docker-compose --version + register: docker_compose_current_version + changed_when: false + failed_when: false + +- name: Delete existing docker-compose version if it's different. + file: + path: "{{ docker_compose_path }}" + state: absent + when: > + docker_compose_current_version.stdout is defined + and docker_compose_version not in docker_compose_current_version.stdout + become: yes + +- name: Install Docker-compose + get_url: + url: https://github.com/docker/compose/releases/download/{{ docker_compose_version }}/docker-compose-Linux-x86_64 + dest: "{{ docker_compose_path }}" + mode: 0755 + become: yes diff --git a/deploy/ansible/appsmith_playbook/roles/base/tasks/main.yml b/deploy/ansible/appsmith_playbook/roles/base/tasks/main.yml new file mode 100644 index 0000000000..429f698cbd --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/base/tasks/main.yml @@ -0,0 +1,27 @@ +--- + - name: Upgrade all packages to the latest version + apt: + name: "*" + state: latest + become: yes + + - name: Install required system packages + apt: + update_cache: yes + name: + - apt-transport-https + - ca-certificates + - curl + - software-properties-common + - python3-pip + - virtualenv + - python3-setuptools + state: latest + become: yes + tags: + - always + + - include_tasks: setup-ubuntu.yml + when: ansible_os_family == 'Debian' + + - include_tasks: install-docker.yml diff --git a/deploy/ansible/appsmith_playbook/roles/base/tasks/setup-ubuntu.yml b/deploy/ansible/appsmith_playbook/roles/base/tasks/setup-ubuntu.yml new file mode 100644 index 0000000000..38e6b49318 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/base/tasks/setup-ubuntu.yml @@ -0,0 +1,31 @@ +--- +- name: Ensure old versions of Docker are not installed. + package: + name: + - docker + - docker-engine + state: absent + +- name: Ensure dependencies are installed. + apt: + name: + - apt-transport-https + - ca-certificates + - gnupg2 + - curl + state: present + become: yes + +- name: Add Docker apt key + shell: > + curl -sSL {{ docker_apt_gpg_key }} | sudo apt-key add - + args: + warn: false + become: yes + +- name: Add Docker repository. + apt_repository: + repo: "{{ docker_apt_repository }}" + state: present + update_cache: true + become: yes diff --git a/deploy/ansible/appsmith_playbook/roles/domain_ssl/handlers/main.yml b/deploy/ansible/appsmith_playbook/roles/domain_ssl/handlers/main.yml new file mode 100644 index 0000000000..301a15bfcf --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/domain_ssl/handlers/main.yml @@ -0,0 +1,9 @@ +--- + - name: Send request to integromat webhook after configure ssl successfully + uri: + url: "{{ analytics_webhook_uri }}" + method: POST + body: "{\"userId\":\"{{ app_installation_id.content }}\",\"event\":\"Configure SSL Successfully\",\"data\":{\"os\":\"{{ os.stdout }}\", \"platform\": \"ansible\"}}" + body_format: json + return_content: yes + listen: "Configure SSL" diff --git a/deploy/ansible/appsmith_playbook/roles/domain_ssl/tasks/main.yml b/deploy/ansible/appsmith_playbook/roles/domain_ssl/tasks/main.yml new file mode 100644 index 0000000000..0fd1143984 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/domain_ssl/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Copy init_letsencrypt template + template: + src="init_letsencrypt.sh.j2" + dest="{{ install_dir }}/init_letsencrypt.sh" + mode="0755" + when: ssl_enable | bool + become: yes + +- name: Run init-letsencrypt + shell: "{{ install_dir}}/init_letsencrypt.sh" + when: ssl_enable | bool + become: yes + notify: "Configure SSL" + + diff --git a/deploy/ansible/appsmith_playbook/roles/domain_ssl/templates/init_letsencrypt.sh.j2 b/deploy/ansible/appsmith_playbook/roles/domain_ssl/templates/init_letsencrypt.sh.j2 new file mode 100644 index 0000000000..5e5b4cd07f --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/domain_ssl/templates/init_letsencrypt.sh.j2 @@ -0,0 +1,80 @@ +#!/bin/bash + +certbot_cmd() { + sudo docker-compose run --rm --entrypoint "$1" certbot +} + +install_dir="{{ install_dir }}" +domain="{{ custom_domain }}" +email="{{ letsencrypt_email }}" +is_ssl_staging="{{ is_ssl_staging }}" + + +echo "Creating certificate for '$domain'." + +rsa_key_size=4096 +data_path="$install_dir/data/certbot" + +sudo chown -R ubuntu:ubuntu "$data_path" + +mkdir -p "$data_path"/{conf,www} + +if ! [[ -e "$data_path/conf/options-ssl-nginx.conf" && -e "$data_path/conf/ssl-dhparams.pem" ]]; then + echo "### Downloading recommended TLS parameters..." + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem" + echo +fi + +echo "### Requesting Let's Encrypt certificate for '$domain'..." + +if [[ -z $email ]]; then + email_arg="--register-unsafely-without-email" +else + email_arg="--email $email --no-eff-email" +fi + +if [[ $is_ssl_staging == "true" ]]; then + staging_arg="--staging" +else + staging_arg="" +fi + + +echo "### Generating OpenSSL key for '$domain'..." +live_path="/etc/letsencrypt/live/$domain" + +cd "$install_dir" + +certbot_cmd \ + "sh -c \"mkdir -p '$live_path' && openssl req -x509 -nodes -newkey rsa:1024 -days 1 \ + -keyout '$live_path/privkey.pem' \ + -out '$live_path/fullchain.pem' \ + -subj '/CN=localhost' \ + \"" +echo + +echo "### Starting nginx..." +sudo docker-compose up --force-recreate --detach nginx +echo + +echo "### Removing key now that validation is done for $domain..." +certbot_cmd \ + "rm -Rfv /etc/letsencrypt/live/$domain /etc/letsencrypt/archive/$domain /etc/letsencrypt/renewal/$domain.conf" +echo + +# The following command exits with a non-zero status code even if the certificate was generated, but some checks failed. +# So we explicitly ignore such failure with a `|| true` in the end, to avoid bash quitting on us because this looks like +# a failed command. +certbot_cmd "certbot certonly --webroot --webroot-path=/var/www/certbot \ + $staging_arg \ + $email_arg \ + --domains $domain \ + --rsa-key-size $rsa_key_size \ + --agree-tos \ + --force-renewal" \ + || true +echo + +echo "### Reloading nginx..." +sudo docker-compose exec nginx nginx -s reload diff --git a/deploy/ansible/appsmith_playbook/roles/generate_template/defaults/main.yml b/deploy/ansible/appsmith_playbook/roles/generate_template/defaults/main.yml new file mode 100644 index 0000000000..d702a88e1a --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/generate_template/defaults/main.yml @@ -0,0 +1,3 @@ +--- +# Default installation dir +install_dir: /home/ubuntu/appsmith \ No newline at end of file diff --git a/deploy/ansible/appsmith_playbook/roles/generate_template/tasks/main.yml b/deploy/ansible/appsmith_playbook/roles/generate_template/tasks/main.yml new file mode 100644 index 0000000000..81c90b7d15 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/generate_template/tasks/main.yml @@ -0,0 +1,40 @@ +--- + +- name: Create installation folder + file: + path: "{{ install_dir }}" + state: directory + +- name: reate mongo and nginx folder if they don't exist + file: + path: "{{ install_dir }}/data/{{ item }}" + state: directory + loop: ["nginx", "mongo"] + +- name: Check for encryption.env + stat: + path: "{{ install_dir }}/encryption.env" + register: encryption_exists + + + +- name: Copy template file + template: src={{ item.src }} dest={{ item.dest }} + loop: + - { src: 'docker-compose.j2', dest: '{{ install_dir }}/docker-compose.yml'} + - { src: 'mongo-init.js.j2', dest: '{{ install_dir }}/data/mongo/init.js'} + - { src: 'docker.env.j2', dest: '{{ install_dir }}/docker.env'} + become: yes + + +- name: Copy encryption template file + template: src="encryption.env.j2" dest="{{ install_dir }}/encryption.env" + when: not encryption_exists.stat.exists + +- set_fact: + ssl_cmt: "{% if ssl_enable == 'false' %}#{% endif %}" + +- name: Copy nginx template file + template: src="nginx-app.conf.j2" dest="{{ install_dir }}/data/nginx/app.conf.template" + become: yes + diff --git a/deploy/ansible/appsmith_playbook/roles/generate_template/templates/docker-compose.j2 b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/docker-compose.j2 new file mode 100644 index 0000000000..8683009851 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/docker-compose.j2 @@ -0,0 +1,80 @@ +version: "3.7" + +services: + nginx: + image: appsmith/appsmith-editor + env_file: ./docker.env + ports: + - "80:80" + - "443:443" + volumes: + - ./data/nginx/app.conf.template:/nginx.conf.template + - ./data/certbot/conf:/etc/letsencrypt + - ./data/certbot/www:/var/www/certbot + command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & /start-nginx.sh'" + depends_on: + - appsmith-internal-server + labels: + com.centurylinklabs.watchtower.enable: "true" + networks: + - appsmith + + certbot: + image: certbot/certbot + volumes: + - ./data/certbot/conf:/etc/letsencrypt + - ./data/certbot/www:/var/www/certbot + entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" + networks: + - appsmith + + appsmith-internal-server: + image: appsmith/appsmith-server:latest + env_file: + - ./docker.env + - ./encryption.env + expose: + - "8080" + links: + - mongo + depends_on: + - mongo + - redis + labels: + com.centurylinklabs.watchtower.enable: "true" + networks: + - appsmith + + mongo: + image: mongo + expose: + - "27017" + environment: + - MONGO_INITDB_DATABASE={{ mongo_database }} + - MONGO_INITDB_ROOT_USERNAME={{ mongo_root_user }} + - MONGO_INITDB_ROOT_PASSWORD={{ mongo_root_password }} + volumes: + - ./data/mongo/db:/data/db + - ./data/mongo/init.js:/docker-entrypoint-initdb.d/init.js:ro + networks: + - appsmith + + redis: + image: redis + expose: + - "6379" + networks: + - appsmith + + watchtower: + image: containrrr/watchtower + volumes: + - /var/run/docker.sock:/var/run/docker.sock + # Update check interval in seconds. + command: --interval 300 --label-enable + networks: + - appsmith + +networks: + appsmith: + driver: bridge \ No newline at end of file diff --git a/deploy/ansible/appsmith_playbook/roles/generate_template/templates/docker.env.j2 b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/docker.env.j2 new file mode 100644 index 0000000000..33a7d71d44 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/docker.env.j2 @@ -0,0 +1,47 @@ +# Read our documentation on how to configure these features +# https://docs.appsmith.com/v/v1.1/enabling-3p-services + +# ***** Email ********** +APPSMITH_MAIL_ENABLED={{ mail_enabled }} +APPSMITH_MAIL_FROM={{ mail_from }} +APPSMITH_REPLY_TO= {{ mail_to }} +APPSMITH_MAIL_HOST= {{ mail_host }} +APPSMITH_MAIL_PORT= {{ mail_port }} +# ***** Set to true if providing a TLS port ****** +APPSMITH_MAIL_SMTP_TLS_ENABLED={{ mail_ssl_enabled}} +APPSMITH_MAIL_USERNAME= {{ mail_username }} +APPSMITH_MAIL_PASSWORD= {{ mail_password }} +APPSMITH_MAIL_SMTP_AUTH= {{ mail_auth }} +# ****************************** + +# ******** Google OAuth ******** + +{% if not google_client_id or not google_secret_id %} +#APPSMITH_OAUTH2_GOOGLE_CLIENT_ID= +#APPSMITH_OAUTH2_GOOGLE_CLIENT_SECRET= +{% else %} +APPSMITH_OAUTH2_GOOGLE_CLIENT_ID={{ google_client_id }} +APPSMITH_OAUTH2_GOOGLE_CLIENT_SECRET={{ google_secret_id }} +{% endif %} +# ****************************** + +# ********* Github OAUth ********** +{% if not google_client_id or not google_secret_id %} +#APPSMITH_OAUTH2_GITHUB_CLIENT_ID= +#APPSMITH_OAUTH2_GITHUB_CLIENT_SECRET= +{% else %} +APPSMITH_OAUTH2_GITHUB_CLIENT_ID={{ github_client_id }} +APPSMITH_OAUTH2_GITHUB_CLIENT_SECRET={{ github_client_secret }} +{% endif %} +# ********************************* + +# ******** Google Maps *********** +APPSMITH_GOOGLE_MAPS_API_KEY= {{ google_maps_api_key}} +# ******************************** + +# ******** Database ************* +APPSMITH_REDIS_URL=redis://redis:6379 +APPSMITH_MONGODB_URI=mongodb://{{ mongo_root_user }}:{{ mongo_root_password }}@{{ mongo_host }}/{{ mongo_database }}?retryWrites=true +# ******************************* + +APPSMITH_DISABLE_TELEMETRY={{ disable_telemetry }} diff --git a/deploy/ansible/appsmith_playbook/roles/generate_template/templates/encryption.env.j2 b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/encryption.env.j2 new file mode 100644 index 0000000000..c198529ae1 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/encryption.env.j2 @@ -0,0 +1,2 @@ +APPSMITH_ENCRYPTION_PASSWORD={{ user_encryption_password }} +APPSMITH_ENCRYPTION_SALT= {{ user_encryption_salt }} \ No newline at end of file diff --git a/deploy/ansible/appsmith_playbook/roles/generate_template/templates/mongo-init.js.j2 b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/mongo-init.js.j2 new file mode 100644 index 0000000000..0d56ab1230 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/mongo-init.js.j2 @@ -0,0 +1,1529 @@ +let error = false +print("**** Going to start Mongo seed ****") + +let res = [ + db.createUser( + { + user: "{{ mongo_root_user }}", + pwd: "{{ mongo_root_password }}", + roles: [{ + role: "readAnyDatabase", + db: "admin" + }, "readWrite"] + } + ), + + db.group.insert({ + "_id" : ObjectId("5df8c225078d501fc3f45361"), + "name" : "Admin", + "displayName" : "Admin", + "organizationId" : "default-org", + "permissions" : [ + "read:groups", + "read:organizations", + "create:users", + "update:users", + "create:groups", + "create:organizations", + "read:users", + "read:pages", + "create:pages", + "update:pages", + "read:layouts", + "create:layouts", + "update:layouts", + "read:properties", + "create:properties", + "update:properties", + "read:actions", + "create:actions", + "update:actions", + "read:resources", + "create:resources", + "update:resources", + "read:plugins", + "create:plugins", + "delete:actions", + "create:collections", + "update:collections", + "delete:collections", + "read:collections", + "create:applications", + "update:applications", + "read:applications", + "read:datasources", + "create:datasources", + "update:datasources", + "read:configs", + "update:configs", + "create:configs", + "delete:applications", + "create:import", + "read:import", + "update:import", + "create:providers", + "read:providers", + "update:providers", + "read:marketplace", + "delete:import", + "delete:pages", + "create:templates", + "update:templates", + "read:templates", + "read:items", + "create:items", + "delete:datasources" + ], + "isDefault" : false, + "deleted" : false, + "_class" : "com.appsmith.server.domains.Group" + }), + + + db.group.insert({ + "_id" : ObjectId("5df8c1fa078d501fc3f44d41"), + "name" : "Member", + "displayName" : "Member", + "organizationId" : "default-org", + "permissions" : [ + "read:groups", + "read:organizations", + "create:users", + "update:users", + "create:groups", + "create:organizations", + "read:users", + "read:pages", + "create:pages", + "update:pages", + "read:layouts", + "create:layouts", + "update:layouts", + "read:properties", + "create:properties", + "update:properties", + "read:actions", + "create:actions", + "update:actions", + "read:resources", + "create:resources", + "update:resources", + "read:plugins", + "create:plugins", + "delete:actions", + "create:collections", + "update:collections", + "delete:collections", + "read:collections", + "create:applications", + "update:applications", + "read:applications", + "read:datasources", + "create:datasources", + "update:datasources", + "read:configs", + "update:configs", + "create:configs", + "delete:applications", + "create:import", + "read:import", + "update:import", + "create:providers", + "read:providers", + "update:providers", + "read:marketplace", + "delete:import", + "delete:pages", + "create:templates", + "update:templates", + "read:templates", + "read:items", + "create:items", + "delete:datasources" + ], + "isDefault" : true, + "deleted" : false, + "_class" : "com.appsmith.server.domains.Group" + }), + + db.group.insert({ + "_id" : ObjectId("5df8c1e0078d501fc3f4491b"), + "name" : "Owner", + "displayName" : "Owner", + "organizationId" : "default-org", + "permissions" : [ + "read:groups", + "read:organizations", + "create:users", + "update:users", + "create:groups", + "create:organizations", + "read:users", + "read:pages", + "create:pages", + "update:pages", + "read:layouts", + "create:layouts", + "update:layouts", + "read:properties", + "create:properties", + "update:properties", + "read:actions", + "create:actions", + "update:actions", + "read:resources", + "create:resources", + "update:resources", + "read:plugins", + "create:plugins", + "delete:actions", + "create:collections", + "update:collections", + "delete:collections", + "read:collections", + "create:applications", + "update:applications", + "read:applications", + "read:datasources", + "create:datasources", + "update:datasources", + "read:configs", + "update:configs", + "create:configs", + "delete:applications", + "create:import", + "read:import", + "update:import", + "create:providers", + "read:providers", + "update:providers", + "read:marketplace", + "delete:import", + "delete:pages", + "create:templates", + "update:templates", + "read:templates", + "read:items", + "create:items", + "delete:datasources" + ], + "isDefault" : false, + "deleted" : false, + "_class" : "com.appsmith.server.domains.Group" + }), + +{% raw %} + db.config.insert({ + "config" : { + "CONTAINER_WIDGET" : [ + { + "id" : "5.1", + "sectionName" : "General", + "children" : [ + { + "id" : "5.1.1", + "helpText" : "Use a html color name, HEX, RGB or RGBA value", + "placeholderText" : "#FFFFFF / Gray / rgb(255, 99, 71)", + "propertyName" : "backgroundColor", + "label" : "Background Color", + "validationType" : "HTML_COLOR", + "errorMessage" : "Invalid HTML color name, HEX, RGB or RGBA value", + "expected" : { + "message" : "HTML colors/HEX/RGB/RGBA value", + "type" : "string" + }, + "controlType" : "INPUT_TEXT" + }, + { + "id" : "5.1.2", + "helpText" : "Controls the visibility of the widget", + "propertyName" : "isVisible", + "label" : "Visible", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "expected" : { + "message" : "Value should evaluate to true or false", + "type" : "Boolean" + }, + "controlType" : "SWITCH", + "isJSConvertible" : true + }, + { + "id" : "5.1.3", + "propertyName" : "shouldScrollContents", + "label" : "Scroll Contents", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "expected" : { + "message" : "Value should evaluate to true or false", + "type" : "Boolean" + }, + "controlType" : "SWITCH" + } + ] + } + ], + "DATE_PICKER_WIDGET" : [ + { + "sectionName" : "General", + "id" : "6.1", + "children" : [ + { + "id" : "6.1.2", + "propertyName" : "defaultDate", + "label" : "Default Date", + "validationType" : "DATE", + "helpText" : "Sets the default date of the widget. The date is updated if the default date changes", + "errorMessage" : "Must be a valid date", + "controlType" : "DATE_PICKER", + "placeholderText" : "Enter Default Date", + "isJSConvertible" : true + }, + { + "id" : "6.1.3", + "propertyName" : "isRequired", + "label" : "Required", + "helpText" : "Disables a form submit button when this widget is empty", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + }, + { + "id" : "6.1.4", + "propertyName" : "isVisible", + "label" : "Visible", + "helpText" : "Controls the visibility of the widget", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + }, + { + "id" : "6.1.5", + "propertyName" : "isDisabled", + "label" : "Disabled", + "validationType" : "BOOLEAN", + "helpText" : "Disables input to this widget", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + } + ] + }, + { + "sectionName" : "Actions", + "id" : "6.2", + "children" : [ + { + "id" : "6.2.1", + "propertyName" : "onDateSelected", + "label" : "onDateSelected", + "controlType" : "ACTION_SELECTOR" + } + ] + } + ], + "TABLE_WIDGET" : [ + { + "id" : "7.1", + "sectionName" : "General", + "children" : [ + { + "id" : "7.1.1", + "helpText" : "Takes in an array of objects to display rows in the table. Bind data from an API using {{}}", + "propertyName" : "tableData", + "label" : "Table Data", + "controlType" : "INPUT_TEXT", + "validationType" : "TABLE_ARRAY", + "errorMessage" : "Requires an array of objects", + "expected" : { + "message" : "An array of objects. Keys are column names", + "type" : "Record[]" + }, + "placeholderText" : "Enter [{ \"col1\": \"val1\" }]", + "inputType" : "ARRAY" + }, + { + "id" : "7.1.2", + "helpText" : "Bind the Table.pageNo property in your API and call it onPageChange", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "propertyName" : "serverSidePaginationEnabled", + "label" : "Server Side Pagination", + "controlType" : "SWITCH" + }, + { + "id" : "7.1.3", + "helpText" : "Controls the visibility of the widget", + "propertyName" : "isVisible", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true, + "label" : "Visible", + "controlType" : "SWITCH", + "expected" : { + "message" : "Value should evaluate to true or false", + "type" : "Boolean" + } + }, + { + "id" : "7.1.4", + "helpText" : "Enable PDF Export", + "propertyName" : "exportPDF", + "label" : "PDF Export", + "controlType" : "SWITCH" + }, + { + "id" : "7.1.5", + "helpText" : "Enable Excel Export", + "propertyName" : "exportExcel", + "label" : "Excel Export", + "controlType" : "SWITCH" + }, + { + "id" : "7.1.6", + "helpText" : "Enable CSV Export", + "propertyName" : "exportCsv", + "label" : "CSV Export", + "controlType" : "SWITCH" + } + ] + }, + { + "id" : "7.2", + "sectionName" : "Actions", + "children" : [ + { + "id" : "7.2.1", + "helpText" : "Adds a button action for every row. Reference the Table.selectedRow property in the action", + "propertyName" : "columnActions", + "label" : "Row Button", + "controlType" : "COLUMN_ACTION_SELECTOR" + }, + { + "id" : "7.2.2", + "helpText" : "Triggers an action when a table row is selected", + "propertyName" : "onRowSelected", + "label" : "onRowSelected", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + }, + { + "id" : "7.2.3", + "helpText" : "Triggers an action when a table page is changed", + "propertyName" : "onPageChange", + "label" : "onPageChange", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "IMAGE_WIDGET" : [ + { + "id" : "3.1", + "sectionName" : "General", + "children" : [ + { + "id" : "3.1.1", + "helpText" : "Renders the url or Base64 in the widget", + "propertyName" : "image", + "label" : "Image", + "controlType" : "INPUT_TEXT", + "validationType" : "URL_BASE64", + "errorMessage" : "Must be a valid url or base64 string", + "placeholderText" : "Enter URL / Base64" + }, + { + "id" : "3.1.2", + "helpText" : "Renders the url or Base64 when no image is provided", + "propertyName" : "defaultImage", + "label" : "Default Image", + "controlType" : "INPUT_TEXT", + "validationType" : "URL_BASE64", + "errorMessage" : "Must be a valid url or base64 string", + "placeholderText" : "Enter URL / Base64" + }, + { + "id" : "3.1.3", + "helpText" : "Controls the visibility of the widget", + "propertyName" : "isVisible", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "label" : "Visible", + "controlType" : "SWITCH", + "isJSConvertible" : true + } + ] + } + ], + "RADIO_GROUP_WIDGET" : [ + { + "id" : "10.1", + "sectionName" : "General", + "children" : [ + { + "id" : "10.1.2", + "helpText" : "Displays a list of options for a user to select. Values must be unique", + "validationType" : "KEY_VAL", + "errorMessage" : "Requires an array of objects with label and value fields", + "propertyName" : "options", + "label" : "Options", + "controlType" : "OPTION_INPUT", + "isJSConvertible" : true + }, + { + "id" : "10.1.3", + "helpText" : "Selects a value of the options entered by default", + "propertyName" : "defaultOptionValue", + "label" : "Default Selected Value", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "placeholderText" : "Enter option value", + "controlType" : "INPUT_TEXT" + }, + { + "id" : "10.1.4", + "helpText" : "Disables a form submit button when this widget is empty", + "propertyName" : "isRequired", + "label" : "Required", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH", + "isJSConvertible" : true + }, + { + "id" : "10.1.5", + "helpText" : "Controls the visibility of the widget", + "propertyName" : "isVisible", + "label" : "Visible", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH", + "isJSConvertible" : true + } + ] + }, + { + "id" : "10.2", + "sectionName" : "Actions", + "children" : [ + { + "id" : "10.2.1", + "helpText" : "Triggers an action when a user changes the selected option", + "propertyName" : "onSelectionChange", + "label" : "onSelectionChange", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "TABS_WIDGET" : [ + { + "id" : "16.1", + "sectionName" : "General", + "children" : [ + { + "id" : "16.1.1", + "helpText" : "Takes an array of tab names to render tabs", + "propertyName" : "tabs", + "isJSConvertible" : true, + "label" : "Tabs", + "controlType" : "TABS_INPUT" + }, + { + "id" : "16.1.2", + "propertyName" : "selectedTab", + "helpText" : "Selects a tab name specified by default", + "placeholderText" : "Enter tab name", + "label" : "Default Tab", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "controlType" : "INPUT_TEXT" + }, + { + "id" : "16.1.5", + "propertyName" : "shouldScrollContents", + "label" : "Scroll Contents", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + }, + { + "id" : "16.1.4", + "propertyName" : "isVisible", + "label" : "Visible", + "helpText" : "Controls the visibility of the widget", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH", + "isJSConvertible" : true + } + ] + } + ], + "CHART_WIDGET" : [ + { + "id" : "13.1", + "sectionName" : "General", + "children" : [ + { + "id" : "13.1.1", + "helpText" : "Adds a title to the chart", + "placeholderText" : "Enter title", + "propertyName" : "chartName", + "label" : "Title", + "controlType" : "INPUT_TEXT" + }, + { + "id" : "13.1.2", + "helpText" : "Changes the visualisation of the chart data", + "propertyName" : "chartType", + "label" : "Chart Type", + "controlType" : "DROP_DOWN", + "options" : [ + { + "label" : "Line Chart", + "value" : "LINE_CHART" + }, + { + "label" : "Bar Chart", + "value" : "BAR_CHART" + }, + { + "label" : "Pie Chart", + "value" : "PIE_CHART" + }, + { + "label" : "Column Chart", + "value" : "COLUMN_CHART" + }, + { + "label" : "Area Chart", + "value" : "AREA_CHART" + } + ], + "isJSConvertible" : true + }, + { + "id" : "13.1.6", + "helpText" : "Populates the chart with the data", + "propertyName" : "singleChartData", + "placeholderText" : "Enter [{ \"x\": \"val\", \"y\": \"val\" }]", + "validationType" : "CHART_ARRAY", + "errorMessage" : "Must be an array of x,y", + "label" : "Chart Data", + "controlType" : "INPUT_TEXT" + }, + { + "id" : "13.1.3", + "helpText" : "Specifies the label of the x-axis", + "propertyName" : "xAxisName", + "placeholderText" : "Enter label text", + "label" : "x-axis Label", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "controlType" : "INPUT_TEXT" + }, + { + "id" : "13.1.5", + "helpText" : "Specifies the label of the y-axis", + "propertyName" : "yAxisName", + "placeholderText" : "Enter label text", + "label" : "y-axis Label", + "controlType" : "INPUT_TEXT" + }, + { + "id" : "13.1.4", + "helpText" : "Enables scrolling inside the chart", + "propertyName" : "allowHorizontalScroll", + "label" : "Allow horizontal scroll", + "controlType" : "SWITCH" + }, + { + "id" : "13.1.7", + "propertyName" : "isVisible", + "label" : "Visible", + "helpText" : "Controls the visibility of the widget", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH", + "isJSConvertible" : true + } + ] + } + ], + "MODAL_WIDGET" : [ + { + "sectionName" : "General", + "id" : "18.1", + "children" : [ + { + "id" : "18.1.1", + "propertyName" : "canOutsideClickClose", + "label" : "Quick Dismiss", + "helpText" : "Allows dismissing the modal when user taps outside", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + }, + { + "id" : "18.1.2", + "propertyName" : "size", + "label" : "Modal Type", + "controlType" : "DROP_DOWN", + "options" : [ + { + "label" : "Form Modal", + "value" : "MODAL_LARGE" + }, + { + "label" : "Alert Modal", + "value" : "MODAL_SMALL" + } + ] + }, + { + "id" : "18.1.3", + "propertyName" : "shouldScrollContents", + "label" : "Scroll Contents", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + } + ] + } + ], + "INPUT_WIDGET" : [ + { + "id" : "4.1", + "sectionName" : "General", + "children" : [ + { + "id" : "4.1.2", + "helpText" : "Changes the type of data captured in the input", + "propertyName" : "inputType", + "label" : "Data Type", + "controlType" : "DROP_DOWN", + "options" : [ + { + "label" : "Text", + "value" : "TEXT" + }, + { + "label" : "Number", + "value" : "NUMBER" + }, + { + "label" : "Password", + "value" : "PASSWORD" + }, + { + "label" : "Phone Number", + "value" : "PHONE_NUMBER" + }, + { + "label" : "Email", + "value" : "EMAIL" + } + ] + }, + { + "id" : "4.1.3", + "helpText" : "Sets a placeholder text for the input", + "propertyName" : "placeholderText", + "label" : "Placeholder", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "controlType" : "INPUT_TEXT", + "placeholderText" : "Enter placeholder text" + }, + { + "id" : "4.1.4", + "helpText" : "Sets the default text of the widget. The text is updated if the default text changes", + "propertyName" : "defaultText", + "label" : "Default Input", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "controlType" : "INPUT_TEXT", + "placeholderText" : "Enter default text" + }, + { + "id" : "4.1.5", + "helpText" : "Adds a validation to the input which displays an error on failure", + "propertyName" : "regex", + "label" : "Regex", + "validationType" : "REGEX", + "errorMessage" : "Must be a valid regex string", + "controlType" : "INPUT_TEXT", + "placeholderText" : "^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$", + "inputType" : "TEXT" + }, + { + "id" : "4.1.6", + "helpText" : "Displays the error message if the regex validation fails", + "propertyName" : "errorMessage", + "label" : "Error Message", + "controlType" : "INPUT_TEXT", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "placeholderText" : "Enter error message", + "inputType" : "TEXT" + }, + { + "id" : "4.1.7", + "propertyName" : "isRequired", + "label" : "Required", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "helpText" : "Disables a form submit button when this widget is empty", + "controlType" : "SWITCH", + "isJSConvertible" : true + }, + { + "id" : "4.1.8", + "helpText" : "Controls the visibility of the widget", + "propertyName" : "isVisible", + "label" : "Visible", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH", + "isJSConvertible" : true + }, + { + "id" : "4.1.9", + "helpText" : "Disables input to this widget", + "propertyName" : "isDisabled", + "label" : "Disabled", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH", + "isJSConvertible" : true + } + ] + }, + { + "id" : "4.2.1", + "sectionName" : "Actions", + "children" : [ + { + "id" : "5.11.2", + "helpText" : "Triggers an action when the text is changed", + "propertyName" : "onTextChanged", + "label" : "onTextChanged", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "DROP_DOWN_WIDGET" : [ + { + "id" : "8.1", + "sectionName" : "General", + "children" : [ + { + "id" : "8.1.2", + "helpText" : "Allows users to select either a single option or multiple options", + "propertyName" : "selectionType", + "label" : "Selection Type", + "controlType" : "DROP_DOWN", + "options" : [ + { + "label" : "Single Select", + "value" : "SINGLE_SELECT" + }, + { + "label" : "Multi Select", + "value" : "MULTI_SELECT" + } + ] + }, + { + "id" : "8.1.3", + "helpText" : "Allows users to select either a single option or multiple options. Values must be unique", + "propertyName" : "options", + "label" : "Options", + "validationType" : "KEY_VAL", + "errorMessage" : "Requires an array of objects with label and value fields", + "controlType" : "INPUT_TEXT", + "placeholderText" : "Enter [{label: \"label1\", value: \"value2\"}]" + }, + { + "id" : "8.1.4", + "helpText" : "Selects the option with value by default", + "propertyName" : "defaultOptionValue", + "label" : "Default Option", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "controlType" : "INPUT_TEXT", + "placeholderText" : "Enter option value" + }, + { + "id" : "8.1.5", + "propertyName" : "isRequired", + "label" : "Required", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "helpText" : "Disables a form submit button when this widget is empty", + "controlType" : "SWITCH", + "isJSConvertible" : true + }, + { + "id" : "8.1.6", + "helpText" : "Controls the visibility of the widget", + "propertyName" : "isVisible", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "label" : "Visible", + "controlType" : "SWITCH", + "isJSConvertible" : true + } + ] + }, + { + "id" : "8.2", + "sectionName" : "Actions", + "children" : [ + { + "id" : "8.2.1", + "helpText" : "Triggers an action when a user selects an option", + "propertyName" : "onOptionChange", + "label" : "onOptionChange", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "FORM_BUTTON_WIDGET" : [ + { + "id" : "15.1", + "sectionName" : "General", + "children" : [ + { + "id" : "15.1.1", + "propertyName" : "text", + "label" : "Label", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "helpText" : "Sets the label of the button", + "controlType" : "INPUT_TEXT", + "placeholderText" : "Enter label text" + }, + { + "id" : "15.1.2", + "propertyName" : "buttonStyle", + "label" : "Button Style", + "helpText" : "Changes the style of the button", + "controlType" : "DROP_DOWN", + "options" : [ + { + "label" : "Primary Button", + "value" : "PRIMARY_BUTTON" + }, + { + "label" : "Secondary Button", + "value" : "SECONDARY_BUTTON" + }, + { + "label" : "Danger Button", + "value" : "DANGER_BUTTON" + } + ] + }, + { + "id" : "15.1.3", + "helpText" : "Disables the button when the parent form has a required widget that is not filled", + "propertyName" : "disabledWhenInvalid", + "label" : "Disabled Invalid Forms", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + }, + { + "id" : "15.1.4", + "helpText" : "Resets the fields within the parent form when the click action succeeds", + "propertyName" : "resetFormOnClick", + "label" : "Reset Form on Success", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH", + "isJSConvertible" : true + }, + { + "id" : "15.1.5", + "propertyName" : "isVisible", + "label" : "Visible", + "helpText" : "Controls the visibility of the widget", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + } + ] + }, + { + "id" : "15.2", + "sectionName" : "Actions", + "children" : [ + { + "id" : "15.2.1", + "helpText" : "Triggers an action when the button is clicked", + "propertyName" : "onClick", + "label" : "onClick", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "MAP_WIDGET" : [ + { + "sectionName" : "General", + "id" : "25.1", + "children" : [ + { + "id" : "25.1.1", + "propertyName" : "mapCenter", + "label" : "Initial location", + "isJSConvertible" : true, + "controlType" : "LOCATION_SEARCH" + }, + { + "id" : "25.1.4", + "propertyName" : "defaultMarkers", + "label" : "Default markers", + "controlType" : "INPUT_TEXT", + "inputType" : "ARRAY", + "helpText" : "Sets the default markers on the map", + "validationType" : "MARKER_ARRAY", + "errorMessage" : "Must be an array of lat, long", + "placeholderText" : "Enter [{ \"lat\": \"val1\", \"long\": \"val2\" }]" + }, + { + "id" : "25.1.2", + "propertyName" : "enableSearch", + "label" : "Enable search location", + "helpText" : "Enables locaton search", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + }, + { + "id" : "25.1.3", + "propertyName" : "enablePickLocation", + "label" : "Enable pick location", + "helpText" : "Allows a user to pick their location", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + }, + { + "id" : "25.1.5", + "propertyName" : "enableCreateMarker", + "label" : "Create new marker", + "helpText" : "Allows users to mark locations on the map", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + }, + { + "id" : "25.1.6", + "propertyName" : "zoomLevel", + "label" : "Zoom Level", + "controlType" : "STEP", + "helpText" : "Changes the default zoom of the map", + "stepType" : "ZOOM_PERCENTAGE" + }, + { + "id" : "25.1.7", + "propertyName" : "isVisible", + "label" : "Visible", + "helpText" : "Controls the visibility of the widget", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + } + ] + }, + { + "id" : "27", + "sectionName" : "Actions", + "children" : [ + { + "id" : "27.1", + "propertyName" : "onMarkerClick", + "label" : "onMarkerClick", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + }, + { + "id" : "27.2", + "propertyName" : "onCreateMarker", + "label" : "onCreateMarker", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "BUTTON_WIDGET" : [ + { + "id" : "1.1", + "sectionName" : "General", + "children" : [ + { + "id" : "1.1.1", + "propertyName" : "text", + "label" : "Label", + "helpText" : "Sets the label of the button", + "controlType" : "INPUT_TEXT", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "placeholderText" : "Enter label text" + }, + { + "id" : "1.1.2", + "propertyName" : "buttonStyle", + "label" : "Button Style", + "controlType" : "DROP_DOWN", + "helpText" : "Changes the style of the button", + "options" : [ + { + "label" : "Primary Button", + "value" : "PRIMARY_BUTTON" + }, + { + "label" : "Secondary Button", + "value" : "SECONDARY_BUTTON" + }, + { + "label" : "Danger Button", + "value" : "DANGER_BUTTON" + } + ] + }, + { + "id" : "1.1.3", + "propertyName" : "isDisabled", + "label" : "Disabled", + "controlType" : "SWITCH", + "helpText" : "Disables clicks to this widget", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + }, + { + "id" : "1.1.4", + "propertyName" : "isVisible", + "label" : "Visible", + "helpText" : "Controls the visibility of the widget", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + } + ] + }, + { + "id" : "1.2.1", + "sectionName" : "Actions", + "children" : [ + { + "id" : "2.1", + "helpText" : "Triggers an action when the button is clicked", + "propertyName" : "onClick", + "label" : "onClick", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "RICH_TEXT_EDITOR_WIDGET" : [ + { + "id" : "12.1", + "sectionName" : "General", + "children" : [ + { + "id" : "12.1.1", + "propertyName" : "defaultText", + "helpText" : "Sets the default text of the widget. The text is updated if the default text changes", + "label" : "Default text", + "controlType" : "INPUT_TEXT", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "placeholderText" : "Enter HTML" + }, + { + "id" : "12.1.2", + "propertyName" : "isVisible", + "label" : "Visible", + "helpText" : "Controls the visibility of the widget", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + }, + { + "id" : "12.1.3", + "propertyName" : "isDisabled", + "label" : "Disable", + "helpText" : "Disables input to this widget", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + } + ] + }, + { + "id" : "12.2", + "sectionName" : "Actions", + "children" : [ + { + "id" : "12.2.1", + "helpText" : "Triggers an action when the text is changed", + "propertyName" : "onTextChange", + "label" : "onTextChange", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "FILE_PICKER_WIDGET" : [ + { + "id" : "11.1", + "sectionName" : "General", + "children" : [ + { + "id" : "11.1.1", + "propertyName" : "label", + "label" : "Label", + "controlType" : "INPUT_TEXT", + "helpText" : "Sets the label of the button", + "placeholderText" : "Enter label text", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "inputType" : "TEXT" + }, + { + "id" : "11.1.2", + "propertyName" : "maxNumFiles", + "label" : "Max No. files", + "helpText" : "Sets the maximum number of files that can be uploaded at once", + "controlType" : "INPUT_TEXT", + "placeholderText" : "Enter no. of files", + "validationType" : "POS_INT", + "errorMessage" : "Must be a valid number", + "inputType" : "INTEGER" + }, + { + "id" : "11.1.3", + "propertyName" : "maxFileSize", + "helpText" : "Sets the maximum size of each file that can be uploaded", + "label" : "Max file size", + "controlType" : "INPUT_TEXT", + "validationType" : "POS_REAL", + "errorMessage" : "Must be a valid number", + "placeholderText" : "File size in mb", + "inputType" : "INTEGER" + }, + { + "id" : "11.1.4", + "propertyName" : "allowedFileTypes", + "helpText" : "Restricts the type of files which can be uploaded", + "label" : "Allowed File Types", + "controlType" : "MULTI_SELECT", + "placeholderText" : "Select file types", + "options" : [ + { + "label" : "Any File", + "value" : "*" + }, + { + "label" : "Images", + "value" : "image/*" + }, + { + "label" : "Videos", + "value" : "video/*" + }, + { + "label" : "Audio", + "value" : "audio/*" + }, + { + "label" : "Text", + "value" : "text/*" + }, + { + "label" : "MS Word", + "value" : ".doc" + }, + { + "label" : "JPEG", + "value" : "image/jpeg" + }, + { + "label" : "PNG", + "value" : ".png" + } + ], + "isJSConvertible" : true + }, + { + "id" : "11.1.5", + "propertyName" : "isRequired", + "label" : "Required", + "controlType" : "SWITCH", + "helpText" : "Disables a form submit button when this widget is empty", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + }, + { + "id" : "11.1.6", + "propertyName" : "isVisible", + "label" : "Visible", + "helpText" : "Controls the visibility of the widget", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + }, + { + "id" : "11.1.7", + "propertyName" : "uploadedFileUrls", + "helpText" : "Stores the url of the uploaded file so that it can be referenced in an action later", + "label" : "Uploaded File URLs", + "controlType" : "INPUT_TEXT", + "placeholderText" : "Enter [ \"url1\", \"url2\" ]", + "validationType" : "STR_ARR", + "errorMessage" : "Must be a valid array of URLs", + "inputType" : "TEXT" + } + ] + }, + { + "id" : "11.2", + "sectionName" : "Actions", + "children" : [ + { + "id" : "11.2.1", + "helpText" : "Triggers an action when the user selects a file. Upload files to a CDN here and store their urls in uploadedFileUrls", + "propertyName" : "onFilesSelected", + "label" : "onFilesSelected", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "CHECKBOX_WIDGET" : [ + { + "id" : "9.1", + "sectionName" : "General", + "children" : [ + { + "id" : "9.1.1", + "propertyName" : "label", + "label" : "Label", + "controlType" : "INPUT_TEXT", + "helpText" : "Displays a label next to the widget", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "placeholderText" : "Enter label text" + }, + { + "id" : "9.1.2", + "propertyName" : "defaultCheckedState", + "label" : "Default Selected", + "helpText" : "Checks / un-checks the checkbox by default. Changes to the default selection update the widget state", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + }, + { + "id" : "9.1.3", + "propertyName" : "isRequired", + "label" : "Required", + "helpText" : "Disables a form submit button when this widget is empty", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + }, + { + "id" : "9.1.4", + "propertyName" : "isDisabled", + "label" : "Disabled", + "controlType" : "SWITCH", + "helpText" : "Disables input to this widget", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + }, + { + "id" : "9.1.5", + "propertyName" : "isVisible", + "label" : "Visible", + "helpText" : "Controls the visibility of the widget", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + } + ] + }, + { + "id" : "9.2", + "sectionName" : "Actions", + "children" : [ + { + "id" : "9.2.1", + "helpText" : "Triggers an action when the check state is changed", + "propertyName" : "onCheckChange", + "label" : "onCheckChange", + "controlType" : "ACTION_SELECTOR", + "isJSConvertible" : true + } + ] + } + ], + "FORM_WIDGET" : [ + { + "id" : "14.1", + "sectionName" : "General", + "children" : [ + { + "id" : "14.1.1", + "propertyName" : "backgroundColor", + "label" : "Background Color", + "helpText" : "Use a html color name, HEX, RGB or RGBA value", + "placeholderText" : "#FFFFFF / Gray / rgb(255, 99, 71)", + "validationType" : "HTML_COLOR", + "errorMessage" : "Invalid HTML color name, HEX, RGB or RGBA value", + "controlType" : "INPUT_TEXT" + }, + { + "id" : "14.1.2", + "helpText" : "Controls the visibility of the widget", + "propertyName" : "isVisible", + "label" : "Visible", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + }, + { + "id" : "14.1.3", + "propertyName" : "shouldScrollContents", + "label" : "Scroll Contents", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + } + ] + } + ], + "TEXT_WIDGET" : [ + { + "id" : "2.1", + "sectionName" : "General", + "children" : [ + { + "id" : "2.1.1", + "propertyName" : "text", + "helpText" : "Sets the text of the widget", + "label" : "Text", + "controlType" : "INPUT_TEXT", + "validationType" : "TEXT", + "errorMessage" : "Must be a valid string", + "placeholderText" : "Enter text", + "expected" : { + "message" : "Text to be displayed", + "type" : "string" + } + }, + { + "id" : "2.1.3", + "propertyName" : "textAlign", + "helpText" : "Sets the alignments of the text", + "label" : "Text Align", + "controlType" : "DROP_DOWN", + "options" : [ + { + "label" : "Left", + "value" : "LEFT" + }, + { + "label" : "Center", + "value" : "CENTER" + }, + { + "label" : "Right", + "value" : "RIGHT" + } + ] + }, + { + "id" : "2.1.2", + "propertyName" : "textStyle", + "helpText" : "Sets the font and style of the text", + "label" : "Text Style", + "controlType" : "DROP_DOWN", + "options" : [ + { + "label" : "Heading", + "value" : "HEADING" + }, + { + "label" : "Label", + "value" : "LABEL" + }, + { + "label" : "Body", + "value" : "BODY" + } + ] + }, + { + "id" : "2.1.3", + "propertyName" : "shouldScroll", + "label" : "Enable Scroll", + "helpText" : "Allows scrolling text instead of truncation", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "controlType" : "SWITCH" + }, + { + "id" : "2.1.4", + "propertyName" : "isVisible", + "helpText" : "Controls the visibility of the widget", + "label" : "Visible", + "controlType" : "SWITCH", + "validationType" : "BOOLEAN", + "errorMessage" : "Must be a valid boolean value", + "isJSConvertible" : true + } + ] + } + ] + }, + "name" : "propertyPane", + "updatedAt" : ISODate("2020-06-02T12:29:11.874Z"), + "deleted" : false, + "_class" : "com.appsmith.server.domains.Config" + }) +{% endraw %} +] + +printjson(res) + +if (error) { + print('Error occurred while inserting the records') +} diff --git a/deploy/ansible/appsmith_playbook/roles/generate_template/templates/nginx-app.conf.j2 b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/nginx-app.conf.j2 new file mode 100644 index 0000000000..3ae1f20490 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/generate_template/templates/nginx-app.conf.j2 @@ -0,0 +1,114 @@ + + server { + listen 80; + server_name {{ custom_domain }}; + client_max_body_size 10m; + + gzip on; + + root /var/www/appsmith; + index index.html index.htm; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + + location / { + try_files $uri /index.html =404; + + sub_filter __APPSMITH_SENTRY_DSN__ '{{ sentry_dns }}'; + sub_filter __APPSMITH_SMART_LOOK_ID__ '{{ smart_look_id }}'; + sub_filter __APPSMITH_OAUTH2_GOOGLE_CLIENT_ID__ '{{ google_client_id }}'; + sub_filter __APPSMITH_OAUTH2_GITHUB_CLIENT_ID__ '{{ github_client_id }}'; + sub_filter __APPSMITH_DISABLE_TELEMETRY__ '{{ disable_telemetry }}'; + sub_filter __APPSMITH_MARKETPLACE_ENABLED__ '{{ marketplace_enabled }}'; + sub_filter __APPSMITH_SEGMENT_KEY__ '{{ segment_key }}'; + sub_filter __APPSMITH_OPTIMIZELY_KEY__ '{{ segment_key }}'; + sub_filter __APPSMITH_ALGOLIA_API_ID__ '{{ algolia_api_id }}'; + sub_filter __APPSMITH_ALGOLIA_SEARCH_INDEX_NAME__ '{{ algolia_search_index_name }}'; + sub_filter __APPSMITH_ALGOLIA_API_KEY__ '{{ algolia_api_key }}'; + sub_filter __APPSMITH_CLIENT_LOG_LEVEL__ '{{ client_log_level }}'; + sub_filter __APPSMITH_GOOGLE_MAPS_API_KEY__ '{{ google_maps_api_key }}'; + sub_filter __APPSMITH_TNC_PP__ '{{ tnc_pp }}'; + sub_filter __APPSMITH_VERSION_ID__ '{{ version_id }}'; + sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '{{ version_release_date }}'; + sub_filter __APPSMITH_INTERCOM_APP_ID__ '{{ intercom_app_id }}'; + sub_filter __APPSMITH_MAIL_ENABLED__ '{{ mail_enabled }}'; + } + + location /f { + proxy_pass https://cdn.optimizely.com/; + } + + location /api { + proxy_pass http://appsmith-internal-server:8080; + } + + location /oauth2 { + proxy_pass http://appsmith-internal-server:8080; + } + + location /login { + proxy_pass http://appsmith-internal-server:8080; + } + } + +{{ ssl_cmt }} server { +{{ ssl_cmt }} listen 443 ssl; +{{ ssl_cmt }} server_name {{ custom_domain }}; + +{{ ssl_cmt }} ssl_certificate /etc/letsencrypt/live/{{ custom_domain }}/fullchain.pem; +{{ ssl_cmt }} ssl_certificate_key /etc/letsencrypt/live/{{ custom_domain }}/privkey.pem; + +{{ ssl_cmt }} include /etc/letsencrypt/options-ssl-nginx.conf; +{{ ssl_cmt }} ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + +{{ ssl_cmt }} proxy_set_header X-Forwarded-Proto $scheme; +{{ ssl_cmt }} proxy_set_header X-Forwarded-Host $host; + +{{ ssl_cmt }} root /var/www/appsmith; +{{ ssl_cmt }} index index.html index.htm; + +{{ ssl_cmt }} location / { +{{ ssl_cmt }} try_files $uri /index.html =404; + +{{ ssl_cmt }} sub_filter __APPSMITH_SENTRY_DSN__ '{{ sentry_dns }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_SMART_LOOK_ID__ '{{ smart_look_id }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_OAUTH2_GOOGLE_CLIENT_ID__ '{{ google_client_id }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_OAUTH2_GITHUB_CLIENT_ID__ '{{ github_client_id }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_DISABLE_TELEMETRY__ '{{ disable_telemetry }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_MARKETPLACE_ENABLED__ '{{ marketplace_enabled }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_SEGMENT_KEY__ '{{ segment_key }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_OPTIMIZELY_KEY__ '{{ segment_key }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_ALGOLIA_API_ID__ '{{ algolia_api_id }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_ALGOLIA_SEARCH_INDEX_NAME__ '{{ algolia_search_index_name }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_ALGOLIA_API_KEY__ '{{ algolia_api_key }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_CLIENT_LOG_LEVEL__ '{{ client_log_level }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_GOOGLE_MAPS_API_KEY__ '{{ google_maps_api_key }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_TNC_PP__ '{{ tnc_pp }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_VERSION_ID__ '{{ version_id }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '{{ version_release_date }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_INTERCOM_APP_ID__ '{{ intercom_app_id }}'; +{{ ssl_cmt }} sub_filter __APPSMITH_MAIL_ENABLED__ '{{ mail_enabled }}'; +{{ ssl_cmt }} } + +{{ ssl_cmt }} location /f { +{{ ssl_cmt }} proxy_pass https://cdn.optimizely.com/; +{{ ssl_cmt }} } + +{{ ssl_cmt }} location /api { +{{ ssl_cmt }} proxy_pass http://appsmith-internal-server:8080; +{{ ssl_cmt }} } + +{{ ssl_cmt }} location /oauth2 { +{{ ssl_cmt }} proxy_pass http://appsmith-internal-server:8080; +{{ ssl_cmt }} } + +{{ ssl_cmt }} location /login { +{{ ssl_cmt }} proxy_pass http://appsmith-internal-server:8080; +{{ ssl_cmt }} } + +{{ ssl_cmt }} } diff --git a/deploy/ansible/appsmith_playbook/roles/start_app/handlers/main.yml b/deploy/ansible/appsmith_playbook/roles/start_app/handlers/main.yml new file mode 100644 index 0000000000..522bbad907 --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/start_app/handlers/main.yml @@ -0,0 +1,9 @@ +--- + - name: Send request to integromat when start app with docker + uri: + url: "{{ analytics_webhook_uri }}" + method: POST + body: "{\"userId\":\"{{ app_installation_id.content }}\",\"event\":\"Start app\",\"data\":{\"os\":\"{{ os.stdout }}\", \"platform\": \"ansible\"}}" + body_format: json + return_content: yes + listen: "Start Appsmith with docker-compose" \ No newline at end of file diff --git a/deploy/ansible/appsmith_playbook/roles/start_app/tasks/main.yml b/deploy/ansible/appsmith_playbook/roles/start_app/tasks/main.yml new file mode 100644 index 0000000000..570b24b4ce --- /dev/null +++ b/deploy/ansible/appsmith_playbook/roles/start_app/tasks/main.yml @@ -0,0 +1,6 @@ +--- + - name: Start Appsmith + shell: docker-compose up --build -d + args: + chdir: "{{ install_dir}}/" + notify: "Start Appsmith with docker-compose"