M8L^el8fjCdZ
zFW`2MLYhZ=otH#nNy!S?IJy8^T3VuN{HqhZ%Vv3UGg}@oSLL$zeEqk{$^NJ*F%>j=
zt-ij{4sp^ks;M+#+BLF!oncI^b&-}#i6k&bLhdpvf&<| ;ZEp)w0a8jQ)
zX8s!iDv$;qZVral^c*g$W#8y~o4W9_t`|l~0qVJyA?$x#T6A}}u<#Wl+E|13*GGf-
z>^^3Vo}QCVNdP+;ikmX^?eEu^dTiwzy=-)El=4V*d70N8P@CR}&EYCvmU~xvRbBmb
z($H~vMX?x4E0?lVu*voDvla{1%H+0CanKL^JMTAn>R!yBz}^FJW%nR&dJ90Hf
zXLMAgvu6Mz%1$We1ZZ83UQ6lUTI?`2
z_rC1Os1*yC9Xa;}r|IRn=IanKRs*obI5j}zWu4Fh9=5zR<8cq8l<4!Ubh>GNfz^`N
zq~Fq&GL&%9$s2B&A>dO1`9J+Qfpb=Lq*4_(1Sh}TZJG%gY7A6Mqp4X@-GoSXCXs!c
zB!gUGSPNb|(pK9%IriMywr;iPk|7U)Pv*mG^j&(MAE!0?cGrHx!Mr9Cbx*zT;x{LJ9~ICkx?J!mLUI)y7)b<0rx9!Jj1J5_7vm`=8}QB^IO
z6DO0g)u-QBQ7Nb{^VVQhjutcMnzb*|pTr7xtZ(<)Xv0*SG&Nu8Vc@hvb4ln0IW)2@
z-%3prWJErA`s;#L1iv-EFV}y-5tTC2lAFg|L|AziW27tUUT^4=wKXhz(u*dxCVl|Z
z=VBr#NN?YubmQllb%tTF?J@s3kKeX)K72ail@&c(+t^pITL_7t?H$aP(^Q4}-a{b7
zl!tj7GZebp1(OWDkTn(&0WtxnL_|ood=}oL)3-1;NRP8DKJIO1I-uf%#Vs2w_iMnf
z()BO>d#V3FMZxg+@5q9{@NdP6%7Okza4^$KQaDJH(rm>Y{&E0o3tM=NnOp3C0pyKs
ADgXcg
literal 0
HcmV?d00001
diff --git a/app/server/scripts/acl-migration/.editorconfig b/app/server/scripts/node/.editorconfig
similarity index 100%
rename from app/server/scripts/acl-migration/.editorconfig
rename to app/server/scripts/node/.editorconfig
diff --git a/app/server/scripts/acl-migration/README.md b/app/server/scripts/node/README.md
similarity index 94%
rename from app/server/scripts/acl-migration/README.md
rename to app/server/scripts/node/README.md
index 71b10e167c..4a71290816 100644
--- a/app/server/scripts/acl-migration/README.md
+++ b/app/server/scripts/node/README.md
@@ -42,7 +42,7 @@ Assuming you have node (>=v12), use the following command to run the migration:
```sh
npm install
-node main.js 'https://localhost/api/v1/' 'mongodb://localhost:27017/mobtools'
+node acl-migration.js 'https://localhost/api/v1/' 'mongodb://localhost:27017/mobtools'
```
The first argument should be a running API endpoint, and the second argument should be a URI to the database that this
diff --git a/app/server/scripts/acl-migration/main.js b/app/server/scripts/node/acl-migration.js
similarity index 100%
rename from app/server/scripts/acl-migration/main.js
rename to app/server/scripts/node/acl-migration.js
diff --git a/app/server/scripts/node/dump-examples-org.js b/app/server/scripts/node/dump-examples-org.js
new file mode 100644
index 0000000000..67321180fd
--- /dev/null
+++ b/app/server/scripts/node/dump-examples-org.js
@@ -0,0 +1,206 @@
+if (process.argv.length !== 5) {
+ console.error("Takes three arguments, the MongoDB URL (like 'mongodb://localhost:27017/mobtools'),\n" +
+ "\tthe encryption salt and the encryption password used by the server connecting to this DB.");
+ process.exit(1);
+}
+
+const MONGODB_URL = process.argv[2];
+const ENCRYPTION_SALT = process.argv[3];
+const ENCRYPTION_PASSWORD = process.argv[4];
+
+const { MongoClient, ObjectID } = require("mongodb");
+const fs = require("fs");
+const path = require("path");
+const CryptoJS = require("crypto-js");
+
+const mongoClient = new MongoClient(MONGODB_URL, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+});
+
+console.time("total time taken");
+main()
+ .then(() => console.log("\nFinished Successfully."))
+ .catch(error => console.error(error))
+ .finally(() => {
+ mongoClient.close();
+ console.timeEnd("total time taken");
+ console.log();
+ });
+
+async function main() {
+ const con = await mongoClient.connect();
+ const db = con.db();
+
+ const pluginPackageNameByIds = {};
+ for (const plugin of await db.collection("plugin").find().toArray()) {
+ pluginPackageNameByIds[plugin._id.toString()] = plugin.packageName;
+ }
+
+ const templateOrganizationId = (await db.collection("config").findOne({name: "template-organization"})).config.organizationId;
+
+ const organization = await db.collection("organization").findOne({_id: ObjectID(templateOrganizationId)});
+
+ const $datasources = await db.collection("datasource")
+ .find({organizationId: templateOrganizationId, deleted: false})
+ .map(datasource => {
+ const datasourceConfiguration = datasource.datasourceConfiguration;
+
+ if (datasourceConfiguration.authentication && datasourceConfiguration.authentication.password) {
+ datasourceConfiguration.authentication.password = decrypt(datasourceConfiguration.authentication.password);
+ }
+
+ return {
+ name: datasource.name,
+ $pluginPackageName: pluginPackageNameByIds[datasource.pluginId],
+ datasourceConfiguration: datasourceConfiguration,
+ invalids: datasource.invalids,
+ deleted: false,
+ policies: [],
+ _class: datasource._class,
+ };
+ })
+ .toArray();
+
+ const allPageIds = [];
+ const allDefaultPageIds = new Set();
+ const $applications = await db.collection("application")
+ .find({organizationId: templateOrganizationId, deleted: false, isPublic: true})
+ .map(application => {
+ allPageIds.push(...application.pages.map(page => ObjectID(page._id)));
+ allDefaultPageIds.add(application.pages.filter(page => page.isDefault)[0]._id.toString());
+ return {
+ name: application.name,
+ isPublic: true,
+ $pages: [],
+ pages: application.pages,
+ deleted: false,
+ policies: [],
+ _class: application._class,
+ };
+ })
+ .toArray();
+
+ const actionsByPageId = {};
+ for (const action of await db.collection("action").find({organizationId: templateOrganizationId, deleted: false}).toArray()) {
+ if (!actionsByPageId[action.pageId]) {
+ actionsByPageId[action.pageId] = [];
+ }
+ let $isEmbedded = typeof action.datasource._id === "undefined";
+ actionsByPageId[action.pageId].push({
+ name: action.name,
+ datasource: {
+ $isEmbedded,
+ name: action.datasource.name,
+ $pluginPackageName: pluginPackageNameByIds[action.datasource.pluginId],
+ datasourceConfiguration: action.datasource.datasourceConfiguration,
+ invalids: action.datasource.invalids,
+ deleted: false,
+ policies: [],
+ },
+ actionConfiguration: action.actionConfiguration,
+ pluginType: action.pluginType,
+ executeOnLoad: action.executeOnLoad,
+ dynamicBindingPathList: action.dynamicBindingPathList,
+ isValid: action.isValid,
+ invalids: action.invalids,
+ jsonPathKeys: action.jsonPathKeys,
+ deleted: false,
+ policies: [],
+ _class: action._class,
+ });
+ }
+
+ const pagesById = {};
+ for (const page of await db.collection("page").find({_id: {$in: allPageIds}}).toArray()) {
+ const pageId = page._id.toString();
+
+ for (const layout of page.layouts) {
+ delete layout._id;
+ for (const actionSet of layout.layoutOnLoadActions) {
+ for (const action of actionSet) {
+ delete action._id;
+ }
+ }
+ for (const actionSet of layout.publishedLayoutOnLoadActions) {
+ for (const action of actionSet) {
+ delete action._id;
+ }
+ }
+ }
+
+ pagesById[pageId] = {
+ name: page.name,
+ $isDefault: allDefaultPageIds.has(pageId),
+ $actions: actionsByPageId[pageId],
+ layouts: page.layouts,
+ deleted: false,
+ policies: [],
+ _class: page._class,
+ };
+ }
+
+ for (const application of $applications) {
+ application.$pages = [];
+ for (const page of application.pages) {
+ application.$pages.push(pagesById[page._id]);
+ }
+ delete application.pages;
+ }
+
+ const finalData = {
+ name: organization.name,
+ organizationSettings: organization.organizationSettings,
+ slug: organization.slug,
+ userRoles: [],
+ deleted: false,
+ policies: [],
+ _class: organization._class,
+ $datasources,
+ $applications,
+ };
+
+ if (finalData.slug !== "example-apps") {
+ console.warn("The slug of the organization in the generated dump is not `example-apps`. This might be significant.");
+ }
+
+ fs.writeFileSync(
+ findExamplesJsonPath(),
+ JSON.stringify(finalData, null, 2)
+ );
+}
+
+function findExamplesJsonPath() {
+ let projectDir = __dirname;
+
+ while (projectDir != null && !fs.existsSync(path.join(projectDir, "appsmith-server"))) {
+ projectDir = path.dirname(projectDir);
+ }
+
+ return path.join(projectDir, "appsmith-server", "src", "main", "resources", "examples-organization.json");
+}
+
+
+/*!
+* Author: flohall
+* date: 2019-11-05
+* file: module/textEncryptor.js
+* Original: .
+*/
+const key = CryptoJS.PBKDF2(ENCRYPTION_PASSWORD, ENCRYPTION_SALT, {
+ keySize: 256 / 32,
+ iterations: 1024
+});
+
+const decryptConfig = {
+ // same as NULL_IV_GENERATOR of AesBytesEncryptor - so encryption creates always same cipher text for same input
+ iv: {words: [0, 0, 0, 0, 0, 0, 0, 0], sigBytes: 0},
+ padding: CryptoJS.pad.Pkcs7,
+ mode: CryptoJS.mode.CBC
+};
+
+function decrypt(text) {
+ return CryptoJS.AES
+ .decrypt({ciphertext: CryptoJS.enc.Hex.parse(text)}, key, decryptConfig)
+ .toString(CryptoJS.enc.Utf8);
+}
diff --git a/app/server/scripts/acl-migration/package-lock.json b/app/server/scripts/node/package-lock.json
similarity index 97%
rename from app/server/scripts/acl-migration/package-lock.json
rename to app/server/scripts/node/package-lock.json
index 06538ef31c..35477ae6e7 100644
--- a/app/server/scripts/acl-migration/package-lock.json
+++ b/app/server/scripts/node/package-lock.json
@@ -45,6 +45,12 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
+ "crypto-js": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
+ "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==",
+ "dev": true
+ },
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
diff --git a/app/server/scripts/acl-migration/package.json b/app/server/scripts/node/package.json
similarity index 70%
rename from app/server/scripts/acl-migration/package.json
rename to app/server/scripts/node/package.json
index eda2d9fdc2..b7321f1943 100644
--- a/app/server/scripts/acl-migration/package.json
+++ b/app/server/scripts/node/package.json
@@ -3,15 +3,16 @@
"version": "1.0.0",
"description": "",
"private": true,
- "main": "main.js",
"scripts": {
- "run": "node main.js"
+ "acl-migration": "node acl-migration.js",
+ "dump-examples-org": "node dump-examples-org.js"
},
"keywords": [],
"author": "Appsmith",
"devDependencies": {
"axios": "^0.19.2",
"axios-cookiejar-support": "^1.0.0",
+ "crypto-js": "^4.0.0",
"mongodb": "^3.5.8",
"tough-cookie": "^4.0.0"
}
diff --git a/deploy/install.sh b/deploy/install.sh
index 25592b37c5..711fcffd93 100755
--- a/deploy/install.sh
+++ b/deploy/install.sh
@@ -3,46 +3,68 @@
set -o errexit
is_command_present() {
- type "$1" >/dev/null 2>&1
+ type "$1" >/dev/null 2>&1
+}
+
+is_mac() {
+ [[ $OSTYPE == darwin* ]]
}
# This function checks if the relevant ports required by Appsmith are available or not
# The script should error out in case they aren't available
check_ports_occupied() {
- ports_occupied="$(
- if [[ "$OSTYPE" == "darwin"* ]]; then
- sudo netstat -anp tcp
- else
- sudo netstat -tupln tcp
- fi | awk '$6 == "LISTEN" && $4 ~ /^.*[.:](80|443)$/' | wc -l | grep -o '[[:digit:]]\+'
- )"
+ local port_check_output
+ local ports_pattern="80|443"
+
+ if is_mac; then
+ port_check_output="$(netstat -anp tcp | awk '$6 == "LISTEN" && $4 ~ /^.*\.('"$ports_pattern"')$/')"
+ elif is_command_present ss; then
+ # The `ss` command seems to be a better/faster version of `netstat`, but is not available on all Linux
+ # distributions by default. Other distributions have `ss` but no `netstat`. So, we try for `ss` first, then
+ # fallback to `netstat`.
+ port_check_output="$(ss --all --numeric --tcp | awk '$1 == "LISTEN" && $4 ~ /^.*:('"$ports_pattern"')$/')"
+ elif is_command_present netstat; then
+ port_check_output="$(netstat --all --numeric --tcp | awk '$6 == "LISTEN" && $4 ~ /^.*:('"$ports_pattern"')$/')"
+ fi
+
+ if [[ -n $port_check_output ]]; then
+ echo "+++++++++++ ERROR ++++++++++++++++++++++"
+ echo "Appsmith requires ports 80 & 443 to be open. Please shut down any other service(s) that may be running on these ports."
+ echo "++++++++++++++++++++++++++++++++++++++++"
+ echo ""
+ bye
+ fi
}
install_docker() {
- if [[ $package_manager -eq apt-get ]];then
- echo "++++++++++++++++++++++++"
- echo "Setting up docker repos"
- sudo $package_manager update --quiet
+ echo "++++++++++++++++++++++++"
+ echo "Setting up docker repos"
- sudo apt-get -y --quiet install gnupg-agent
+ if [[ $package_manager == apt-get ]]; then
+ apt_cmd="sudo apt-get --yes --quiet"
+ $apt_cmd update
+ $apt_cmd install gnupg-agent
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
- "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
- $(lsb_release -cs) \
- stable"
+ "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
+ $apt_cmd update
+ echo "Installing docker"
+ $apt_cmd install docker-ce docker-ce-cli containerd.io
+
else
- sudo yum install -y yum-utils
+ yum_cmd="sudo yum --assumeyes --quiet"
+ $yum_cmd install yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
+ echo "Installing docker"
+ $yum_cmd install docker-ce docker-ce-cli containerd.io
+
fi
- sudo ${package_manager} -y update --quiet
- echo "Installing docker"
- sudo ${package_manager} -y install docker-ce docker-ce-cli containerd.io --quiet
}
install_docker_compose() {
- if [ $package_manager == "apt-get" -o $package_manager == "yum" ];then
- if [ ! -f /usr/bin/docker-compose ];then
+ if [[ $package_manager == "apt-get" || $package_manager == "yum" ]]; then
+ if [[ ! -f /usr/bin/docker-compose ]];then
echo "Installing docker-compose..."
sudo curl -L "https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
@@ -67,29 +89,29 @@ start_docker() {
}
check_os() {
- if [[ "$OSTYPE" == "darwin"* ]]; then
+ if is_mac; then
package_manager="brew"
desired_os=1
return
fi
- os_name=`cat /etc/*-release | egrep "^NAME="`
- os_name="${os_name#*=}"
+ os_name="$(cat /etc/*-release | awk -F= '$1 == "NAME" { gsub(/"/, ""); print $2; exit }')"
- case "${os_name}" in
- \"Ubuntu*\")
+ case "$os_name" in
+ Ubuntu*)
desired_os=1
package_manager="apt-get"
;;
- \"Red\ Hat*\")
- desired_os=0
+ Red\ Hat*)
+ desired_os=1
package_manager="yum"
;;
- \"CentOS*\")
- desired_os=0
+ CentOS*)
+ desired_os=1
package_manager="yum"
;;
- *) desired_os=0
+ *)
+ desired_os=0
esac
}
@@ -98,16 +120,11 @@ overwrite_file() {
local template_file="$2"
local full_path="$install_dir/$relative_path"
- if [[ -f $full_path ]]; then
- read -p "File $relative_path already exists. Would you like to replace it? [Y]: " value
- value=${value:-Y}
-
- if ! [[ $value == "Y" || $value == "y" || $value == "yes" || $value == "Yes" ]]; then
- echo "You chose not to replace existing file: '$full_path'."
- rm -f "$template_file"
- echo "File $template_file removed from source directory."
- echo ""
- fi
+ if [[ -f $full_path ]] && ! confirm y "File $relative_path already exists. Would you like to replace it?"; then
+ echo "You chose NOT to replace existing file: '$full_path'."
+ rm -f "$template_file"
+ echo "File $template_file removed from source directory."
+ echo ""
else
mv -f "$template_file" "$full_path"
echo "File $full_path moved successfully!"
@@ -116,30 +133,28 @@ overwrite_file() {
# This function prompts the user for an input for a non-empty Mongo root password.
read_mongo_password() {
- read -sp 'Set the mongo password: ' mongo_root_password
- while [[ -z $mongo_root_password ]]
- do
+ read -srp 'Set the mongo password: ' mongo_root_password
+ while [[ -z $mongo_root_password ]]; do
echo ""
echo ""
echo "+++++++++++ ERROR ++++++++++++++++++++++"
echo "The mongo password cannot be empty. Please input a valid password string."
echo "++++++++++++++++++++++++++++++++++++++++"
echo ""
- read -sp 'Set the mongo password: ' mongo_root_password
+ read -srp 'Set the mongo password: ' mongo_root_password
done
}
# This function prompts the user for an input for a non-empty Mongo username.
read_mongo_username() {
- read -p 'Set the mongo root user: ' mongo_root_user
- while [[ -z $mongo_root_user ]]
- do
+ read -rp 'Set the mongo root user: ' mongo_root_user
+ while [[ -z $mongo_root_user ]]; do
echo ""
echo "+++++++++++ ERROR ++++++++++++++++++++++"
echo "The mongo username cannot be empty. Please input a valid username string."
echo "++++++++++++++++++++++++++++++++++++++++"
echo ""
- read -p 'Set the mongo root user: ' mongo_root_user
+ read -rp 'Set the mongo root user: ' mongo_root_user
done
}
@@ -163,7 +178,7 @@ wait_for_containers_start() {
urlencode() {
# urlencode
- old_lc_collate=$LC_COLLATE
+ local old_lc_collate="$LC_COLLATE"
LC_COLLATE=C
local length="${#1}"
@@ -175,13 +190,122 @@ urlencode() {
esac
done
- LC_COLLATE=$old_lc_collate
+ LC_COLLATE="$old_lc_collate"
+}
+
+generate_password() {
+ # Picked up the following method of generation from : https://gist.github.com/earthgecko/3089509
+ LC_CTYPE=C tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 13 | head -n 1
+}
+
+confirm() {
+ local default="$1" # Should be `y` or `n`.
+ local prompt="$2"
+
+ local options="y/N"
+ if [[ $default == y || $default == Y ]]; then
+ options="Y/n"
+ fi
+
+ local answer
+ read -n1 -rp "$prompt [$options] " answer
+ if [[ -z $answer ]]; then
+ # No answer given, the user just hit the Enter key. Take the default value as the answer.
+ answer="$default"
+ else
+ # An answer was given. This means the user didn't get to hit Enter so the cursor on the same line. Do an empty
+ # echo so the cursor moves to a new line.
+ echo
+ fi
+
+ [[ yY =~ $answer ]]
+}
+
+init_ssl_cert() {
+ local domain="$1"
+ echo "Creating certificate for '$domain'."
+
+ local rsa_key_size=4096
+ local data_path="./data/certbot"
+
+ if [[ -d "$data_path" ]]; then
+ if ! confirm n "Existing certificate data found at '$data_path'. Continue and replace existing certificate?"; then
+ return
+ fi
+ fi
+
+ 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'..."
+
+ local email
+ read -rp 'Enter email address to create SSL certificate: (Optional, but strongly recommended): ' email
+ if [[ -z $email ]]; then
+ local email_arg="--register-unsafely-without-email"
+ else
+ local email_arg="--email $email --no-eff-email"
+ fi
+
+ if confirm n 'Do you want to create certificate in staging mode (which is used for dev purposes and is not subject to rate limits)?'; then
+ local staging_arg="--staging"
+ else
+ local staging_arg=""
+ fi
+
+ echo "### Generating OpenSSL key for '$domain'..."
+ local live_path="/etc/letsencrypt/live/$domain"
+ 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
+}
+
+certbot_cmd() {
+ sudo docker-compose run --rm --entrypoint "$1" certbot
+}
+
+echo_contact_support() {
+ echo "Please contact with your OS details and version${1:-.}"
}
bye() { # Prints a friendly good bye message and exits the script.
- echo ""
- echo -e "Exiting for now. Bye! \U1F44B"
- exit
+ echo -e "\nExiting for now. Bye! \U1F44B\n"
+ exit 1
}
echo -e "\U1F44B Thank you for trying out Appsmith! "
@@ -196,32 +320,38 @@ check_os
if [[ $desired_os -eq 0 ]];then
echo ""
echo "This script is currently meant to install Appsmith on Mac OS X | Ubuntu | RHEL | CentOS machines."
- echo "Please contact support@appsmith.com with your OS details if you wish to extend this support"
+ echo_contact_support " if you wish to extend this support."
bye
else
echo "You're on an OS that is supported by this installation script."
echo ""
fi
-if [[ "$OSTYPE" == "darwin"* && "$EUID" -eq 0 ]]; then
- echo "Please do not run this script with root permissions on macOS."
- echo "Please contact support@appsmith.com with your OS details if you wish to extend this support"
+if [[ $EUID -eq 0 ]]; then
+ echo "Please do not run this script as root/sudo."
+ echo_contact_support
bye
fi
check_ports_occupied
-if [[ $ports_occupied -ne 0 ]]; then
- echo "+++++++++++ ERROR ++++++++++++++++++++++"
- echo "Appsmith requires ports 80 & 443 to be open. Please shut down any other service(s) that may be running on these ports."
- echo "++++++++++++++++++++++++++++++++++++++++"
- echo ""
- bye
+read -rp 'Installation Directory [appsmith]: ' install_dir
+install_dir="${install_dir:-appsmith}"
+if [[ $install_dir != /* ]]; then
+ # If it's not an absolute path, prepend current working directory to it, to make it an absolute path.
+ install_dir="$PWD/$install_dir"
+fi
+
+if [[ -e "$install_dir" ]]; then
+ echo "The path '$install_dir' is already present. Please run the script again with a different path to install new."
+ echo "If you're trying to update your existing installation, that happens automatically through WatchTower."
+ echo_contact_support " if you're facing problems with the auto-updates."
+ exit
fi
# Check is Docker daemon is installed and available. If not, the install & start Docker for Linux machines. We cannot automatically install Docker Desktop on Mac OS
-if ! is_command_present docker ;then
- if [ $package_manager == "apt-get" -o $package_manager == "yum" ];then
+if ! is_command_present docker; then
+ if [[ $package_manager == "apt-get" || $package_manager == "yum" ]]; then
install_docker
else
echo ""
@@ -229,7 +359,7 @@ if ! is_command_present docker ;then
echo "Docker Desktop must be installed manually on Mac OS to proceed. Docker can only be installed automatically on Ubuntu / Redhat / Cent OS"
echo "https://docs.docker.com/docker-for-mac/install/"
echo "++++++++++++++++++++++++++++++++++++++++++++++++"
- exit
+ exit 1
fi
fi
@@ -243,46 +373,40 @@ if [[ $package_manager == "yum" || $package_manager == "apt-get" ]]; then
start_docker
fi
-read -p 'Installation Directory [appsmith]: ' install_dir
-install_dir="${install_dir:-appsmith}"
-mkdir -p "$PWD/$install_dir"
-install_dir="$PWD/$install_dir"
-read -p 'Is this a fresh installation? [Y/n]' fresh_install
-fresh_install="${fresh_install:-Y}"
+echo "Installing Appsmith to '$install_dir'."
+mkdir -p "$install_dir"
echo ""
-if [ $fresh_install == "N" -o $fresh_install == "n" -o $fresh_install == "no" -o $fresh_install == "No" ];then
- read -p 'Enter your current mongo db host: ' mongo_host
- read -p 'Enter your current mongo root user: ' mongo_root_user
- read -sp 'Enter your current mongo password: ' mongo_root_password
- read -p 'Enter your current mongo database name: ' mongo_database
- # It is possible that this isn't the first installation.
- echo ""
- read -p 'Do you have any existing data in the database?[Y/n]: ' existing_encrypted_data
- existing_encrypted_data=${existing_encrypted_data:-Y}
- # In this case be more cautious of auto generating the encryption keys. Err on the side of not generating the encryption keys
- if [ $existing_encrypted_data == "N" -o $existing_encrypted_data == "n" -o $existing_encrypted_data == "no" -o $existing_encrypted_data == "No" ];then
- auto_generate_encryption="true"
- else
- auto_generate_encryption="false"
- fi
-elif [ $fresh_install == "Y" -o $fresh_install == "y" -o $fresh_install == "yes" -o $fresh_install == "Yes" ];then
- echo "Appsmith needs to create a mongo db"
+if confirm y "Is this a fresh installation?"; then
+ echo "Appsmith needs to create a MongoDB instance."
mongo_host="mongo"
mongo_database="appsmith"
-
+
# We invoke functions to read the mongo credentials from the user because they MUST be non-empty
read_mongo_username
read_mongo_password
# Since the mongo was automatically setup, this must be the first time installation. Generate encryption credentials for this scenario
auto_generate_encryption="true"
+else
+ read -rp 'Enter your current mongo db host: ' mongo_host
+ read -rp 'Enter your current mongo root user: ' mongo_root_user
+ read -srp 'Enter your current mongo password: ' mongo_root_password
+ read -rp 'Enter your current mongo database name: ' mongo_database
+ # It is possible that this isn't the first installation.
+ echo ""
+ # In this case be more cautious of auto generating the encryption keys. Err on the side of not generating the encryption keys
+ if confirm y "Do you have any existing data in the database?"; then
+ auto_generate_encryption="false"
+ else
+ auto_generate_encryption="true"
+ fi
fi
echo ""
# urlencoding the Mongo username and password
-encoded_mongo_root_user=$( urlencode $mongo_root_user )
-encoded_mongo_root_password=$( urlencode $mongo_root_password )
+encoded_mongo_root_user=$(urlencode "$mongo_root_user")
+encoded_mongo_root_password=$(urlencode "$mongo_root_password")
encryptionEnv=./template/encryption.env
if test -f "$encryptionEnv"; then
@@ -290,14 +414,14 @@ if test -f "$encryptionEnv"; then
echo "1) No. Conserve the older encryption password and salt and continue"
echo "2) Yes. Overwrite the existing encryption (NOT SUGGESTED) with autogenerated encryption password and salt"
echo "3) Yes. Overwrite the existing encryption (NOT SUGGESTED) with manually entering the encryption password and salt"
- read -p 'Enter option number [1]: ' overwrite_encryption
+ read -rp 'Enter option number [1]: ' overwrite_encryption
overwrite_encryption=${overwrite_encryption:-1}
auto_generate_encryption="false"
if [[ $overwrite_encryption -eq 1 ]];then
setup_encryption="false"
elif [[ $overwrite_encryption -eq 2 ]];then
setup_encryption="true"
- auto_generate_encryption="true"
+ auto_generate_encryption="true"
elif [[ $overwrite_encryption -eq 3 ]];then
setup_encryption="true"
auto_generate_encryption="false"
@@ -309,22 +433,17 @@ fi
if [[ "$setup_encryption" = "true" ]];then
if [[ "$auto_generate_encryption" = "false" ]];then
echo "Please enter the salt and password found in the encyption.env file of your previous appsmith installation "
- read -p 'Enter your encryption password: ' user_encryption_password
- read -p 'Enter your encryption salt: ' user_encryption_salt
- elif [[ "$auto_generate_encryption" = "true" ]];then
- # Picked up the following method of generation from : https://gist.github.com/earthgecko/3089509
- user_encryption_password=$(cat /dev/urandom | LC_CTYPE=C tr -dc 'a-zA-Z0-9' | fold -w 13 | head -n 1)
- user_encryption_salt=$(cat /dev/urandom | LC_CTYPE=C tr -dc 'a-zA-Z0-9' | fold -w 13 | head -n 1)
+ read -rp 'Enter your encryption password: ' user_encryption_password
+ read -rp 'Enter your encryption salt: ' user_encryption_salt
+ elif [[ "$auto_generate_encryption" = "true" ]]; then
+ user_encryption_password=$(generate_password)
+ user_encryption_salt=$(generate_password)
fi
fi
echo ""
-read -p 'Do you have a custom domain that you would like to link? (Only for cloud installations) [N/y]: ' setup_domain
-setup_domain=${setup_domain:-N}
-# Setting default value for the setup_ssl variable. Without this, the script errors out in the if condition later
-setup_ssl='N'
-if [ $setup_domain == "Y" -o $setup_domain == "y" -o $setup_domain == "yes" -o $setup_domain == "Yes" ];then
+if confirm n "Do you have a custom domain that you would like to link? (Only for cloud installations)"; then
echo ""
echo "+++++++++++ IMPORTANT PLEASE READ ++++++++++++++++++++++"
echo "Please update your DNS records with your domain registrar"
@@ -333,12 +452,9 @@ if [ $setup_domain == "Y" -o $setup_domain == "y" -o $setup_domain == "yes" -o $
echo "+++++++++++++++++++++++++++++++++++++++++++++++"
echo ""
echo "Would you like to provision an SSL certificate for your custom domain / subdomain?"
- read -p '(Your DNS records must be updated for us to proceed) [Y/n]: ' setup_ssl
- setup_ssl=${setup_ssl:-Y}
-fi
-
-if [ $setup_ssl == "Y" -o $setup_ssl == "y" -o $setup_ssl == "yes" -o $setup_ssl == "Yes" ]; then
- read -p 'Enter the domain or subdomain on which you want to host appsmith (example.com / app.example.com): ' custom_domain
+ if confirm y '(Your DNS records must be updated for us to proceed)'; then
+ read -rp 'Enter the domain or subdomain on which you want to host appsmith (example.com / app.example.com): ' custom_domain
+ fi
fi
NGINX_SSL_CMNT=""
@@ -348,39 +464,35 @@ fi
echo ""
echo "Downloading the configuration templates..."
-mkdir -p template
-( cd template
-curl --remote-name-all --silent --show-error \
- https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/docker-compose.yml.sh \
- https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/init-letsencrypt.sh.sh \
- https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/mongo-init.js.sh \
- https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/docker.env.sh \
- https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/nginx_app.conf.sh \
- https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/encryption.env.sh
+templates_dir="$(mktemp -d)"
+mkdir -p "$templates_dir"
+
+(
+ cd "$templates_dir"
+ curl --remote-name-all --silent --show-error \
+ https://raw.githubusercontent.com/appsmithorg/appsmith/master/deploy/template/docker-compose.yml.sh \
+ https://raw.githubusercontent.com/appsmithorg/appsmith/master/deploy/template/mongo-init.js.sh \
+ https://raw.githubusercontent.com/appsmithorg/appsmith/master/deploy/template/docker.env.sh \
+ https://raw.githubusercontent.com/appsmithorg/appsmith/master/deploy/template/nginx_app.conf.sh \
+ https://raw.githubusercontent.com/appsmithorg/appsmith/master/deploy/template/encryption.env.sh
)
-# Role - Folder
-for directory_name in nginx certbot/conf certbot/www mongo/db
-do
- mkdir -p "$install_dir/data/$directory_name"
-done
+# Create needed folder structure.
+mkdir -p "$install_dir/data/"{nginx,mongo/db}
echo ""
echo "Generating the configuration files from the templates"
-. ./template/nginx_app.conf.sh
-. ./template/docker-compose.yml.sh
-. ./template/mongo-init.js.sh
-. ./template/init-letsencrypt.sh.sh
-. ./template/docker.env.sh
-if [[ "$setup_encryption" = "true" ]];then
- . ./template/encryption.env.sh
+bash "$templates_dir/nginx_app.conf.sh" "$NGINX_SSL_CMNT" "$custom_domain" > nginx_app.conf
+bash "$templates_dir/docker-compose.yml.sh" "$mongo_root_user" "$mongo_root_password" "$mongo_database" > docker-compose.yml
+bash "$templates_dir/mongo-init.js.sh" "$mongo_root_user" "$mongo_root_password" > mongo-init.js
+bash "$templates_dir/docker.env.sh" "$encoded_mongo_root_user" "$encoded_mongo_root_password" "$mongo_host" > docker.env
+if [[ "$setup_encryption" = "true" ]]; then
+ bash "$templates_dir/encryption.env.sh" "$user_encryption_password" "$user_encryption_salt" > encryption.env
fi
-chmod 0755 init-letsencrypt.sh
overwrite_file "data/nginx/app.conf.template" "nginx_app.conf"
overwrite_file "docker-compose.yml" "docker-compose.yml"
overwrite_file "data/mongo/init.js" "mongo-init.js"
-overwrite_file "init-letsencrypt.sh" "init-letsencrypt.sh"
overwrite_file "docker.env" "docker.env"
overwrite_file "encryption.env" "encryption.env"
@@ -388,12 +500,13 @@ echo ""
cd "$install_dir"
if [[ -n $custom_domain ]]; then
- echo "Running init-letsencrypt.sh..."
- sudo ./init-letsencrypt.sh
+ init_ssl_cert "$custom_domain"
else
echo "No domain found. Skipping generation of SSL certificate."
fi
+rm -rf "$templates_dir"
+
echo ""
echo "Pulling the latest container images"
sudo docker-compose pull
@@ -430,5 +543,4 @@ else
echo "Join our Discord server https://discord.com/invite/rBTTVJp"
fi
-echo ""
-echo -e "Peace out \U1F596\n"
+echo -e "\nPeace out \U1F596\n"
diff --git a/deploy/template/docker-compose.yml.sh b/deploy/template/docker-compose.yml.sh
index f701eaaef6..21b15d0a52 100644
--- a/deploy/template/docker-compose.yml.sh
+++ b/deploy/template/docker-compose.yml.sh
@@ -1,10 +1,12 @@
-#!/bin/sh
+#!/bin/bash
-if [ ! -f docker-compose.yml ]; then
- touch docker-compose.yml
-fi
+set -o nounset
-cat >| docker-compose.yml << EOF
+mongo_root_user="$1"
+mongo_root_password="$2"
+mongo_database="$3"
+
+cat <| docker.env << EOF
+encoded_mongo_root_user="$1"
+encoded_mongo_root_password="$2"
+mongo_host="$3"
+
+cat << EOF
# Read our documentation on how to configure these features
# https://docs.appsmith.com/v/v1.1/enabling-3p-services
diff --git a/deploy/template/encryption.env.sh b/deploy/template/encryption.env.sh
index 27c64b9237..1c4b34cb82 100644
--- a/deploy/template/encryption.env.sh
+++ b/deploy/template/encryption.env.sh
@@ -1,11 +1,11 @@
-#!/bin/sh
+#!/bin/bash
-if [ ! -f encryption.env ]; then
- touch encryption.env
-fi
+set -o nounset
-cat >| encryption.env << EOF
+user_encryption_password="$1"
+user_encryption_salt="$2"
+
+cat <| init-letsencrypt.sh << EOF
-#!/bin/bash
-
-if ! [ -x "\$(command -v docker-compose)" ]; then
- echo 'Error: docker-compose is not installed.' >&2
- exit 1
-fi
-
-domains=($custom_domain)
-rsa_key_size=4096
-data_path="./data/certbot"
-email="" # Adding a valid address is strongly recommended
-staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits
-
-echo "\$data_path"
-if [ -d "\$data_path" ]; then
- read -p "Existing data found for \$domains. Continue and replace existing certificate? [y/N] " decision
- if [ "\$decision" != "Y" ] && [ "\$decision" != "y" ]; then
- exit
- fi
-fi
-
-
-if [ ! -e "\$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "\$data_path/conf/ssl-dhparams.pem" ]; then
- echo "### Downloading recommended TLS parameters ..."
- mkdir -p "\$data_path/conf"
- 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 "### Creating dummy certificate for \$domains ..."
-path="/etc/letsencrypt/live/\$domains"
-mkdir -p "\$data_path/conf/live/\$domains"
-docker-compose run --rm --entrypoint "\\
- openssl req -x509 -nodes -newkey rsa:1024 -days 1\\
- -keyout '\$path/privkey.pem' \\
- -out '\$path/fullchain.pem' \\
- -subj '/CN=localhost'" certbot
-echo
-
-
-echo "### Starting nginx ..."
-docker-compose up --force-recreate -d nginx
-echo
-
-echo "### Deleting dummy certificate for \$domains ..."
-docker-compose run --rm --entrypoint "\\
- rm -Rf /etc/letsencrypt/live/\$domains && \\
- rm -Rf /etc/letsencrypt/archive/\$domains && \\
- rm -Rf /etc/letsencrypt/renewal/\$domains.conf" certbot
-echo
-
-
-echo "### Requesting Let's Encrypt certificate for \$domains ..."
-#Join \$domains to -d args
-domain_args=""
-for domain in "\${domains[@]}"; do
- domain_args="\$domain_args -d \$domain"
-done
-
-# Select appropriate email arg
-case "\$email" in
- "") email_arg="--register-unsafely-without-email" ;;
- *) email_arg="--email \$email" ;;
-esac
-
-# Enable staging mode if needed
-if [ \$staging != "0" ]; then staging_arg="--staging"; fi
-
-docker-compose run --rm --entrypoint "\\
- certbot certonly --webroot -w /var/www/certbot \\
- \$staging_arg \\
- \$email_arg \\
- \$domain_args \\
- --rsa-key-size \$rsa_key_size \\
- --agree-tos \\
- --force-renewal" certbot
-echo
-
-echo "### Reloading nginx ..."
-docker-compose exec nginx nginx -s reload
-
-
-EOF
diff --git a/deploy/template/mongo-init.js.sh b/deploy/template/mongo-init.js.sh
index b88b0bfef9..0b26c98456 100644
--- a/deploy/template/mongo-init.js.sh
+++ b/deploy/template/mongo-init.js.sh
@@ -1,12 +1,11 @@
-#!/bin/sh
+#!/bin/bash
-if [ ! -f mongo-init.js ]; then
- touch mongo-init.js
-fi
+set -o nounset
+mongo_root_user="$1"
+mongo_root_password="$2"
-
-cat >| mongo-init.js << EOF
+cat << EOF
let error = false
print("**** Going to start Mongo seed ****")
@@ -1534,7 +1533,4 @@ printjson(res)
if (error) {
print('Error occurred while inserting the records')
}
-
-
-
EOF
diff --git a/deploy/template/nginx_app.conf.sh b/deploy/template/nginx_app.conf.sh
index 78cca642a9..14c27a9eeb 100644
--- a/deploy/template/nginx_app.conf.sh
+++ b/deploy/template/nginx_app.conf.sh
@@ -1,15 +1,16 @@
-#!/bin/sh
+#!/bin/bash
-if [ ! -f nginx_app.conf ]; then
- touch nginx_app.conf
-fi
+set -o nounset
-# This template file is different from the others because of the sub_filter commands in the Nginx configuration
-# Those variables are substituted inside the Docker container for appsmith-editor during bootup.
-# Hence we wish to prevent environment substitution here.
-# Relevant variables will be replaced at the end of this file via sed command
+# In the config file, there's three types of variables, all represented with the syntax `$name`. The ones that are not
+# escaped with a backslash are rendered within this script. Among the ones that are escaped with a backslash, the ones
+# starting with `APPSMITH_` will be rendered at boot-up time by appsmith-editor docker container. The rest (like $scheme
+# and $host) are for nginx to work out.
-content='
+NGINX_SSL_CMNT="$1"
+custom_domain="$2"
+
+cat <| nginx_app.conf
+EOF
|