feat: Caddy (#28081)
This PR replaces NGINX and Certbot with Caddy. 1. Auto-HTTPS when custom domain is set, is handled by Caddy. 2. If past certs exist, that were provisioned by Certbot in older Appsmith versions, we configure Caddy to make use of them. But this only applies if the certs aren't already expired. If they're expired, point 1 applies. 3. If custom certs are provided in `ssl` folder, Caddy will be configured to use them. 4. Incoming `Forwarded` header is not passed to any reverse proxies. So redirect URL is correctly computed on Google Cloud Run. 5. All other route configurations are exactly as they are in NGINX today. Caddy configuration file is generated in the `caddy-reconfigure.mjs` script, which will also reload Caddy with the new configuration.
This commit is contained in:
parent
54012c5c86
commit
4d24aba331
|
|
@ -29,13 +29,12 @@ ENV PATH /opt/appsmith/utils/node_modules/.bin:/opt/java/bin:/opt/node/bin:$PATH
|
||||||
|
|
||||||
RUN cd ./utils && npm install --only=prod && npm install --only=prod -g . && cd - \
|
RUN cd ./utils && npm install --only=prod && npm install --only=prod -g . && cd - \
|
||||||
&& chmod 0644 /etc/cron.d/* \
|
&& chmod 0644 /etc/cron.d/* \
|
||||||
&& chmod +x *.sh templates/nginx-app.conf.sh /watchtower-hooks/*.sh \
|
&& chmod +x *.sh /watchtower-hooks/*.sh \
|
||||||
# Disable setuid/setgid bits for the files inside container.
|
# Disable setuid/setgid bits for the files inside container.
|
||||||
&& find / \( -path /proc -prune \) -o \( \( -perm -2000 -o -perm -4000 \) -print -exec chmod -s '{}' + \) || true \
|
&& find / \( -path /proc -prune \) -o \( \( -perm -2000 -o -perm -4000 \) -print -exec chmod -s '{}' + \) || true \
|
||||||
&& node prepare-image.mjs \
|
|
||||||
&& mkdir -p /.mongodb/mongosh /appsmith-stacks \
|
&& mkdir -p /.mongodb/mongosh /appsmith-stacks \
|
||||||
&& chmod ugo+w /etc /appsmith-stacks \
|
&& chmod ugo+w /etc /appsmith-stacks \
|
||||||
&& chmod -R ugo+w /var/lib/nginx /var/log/nginx /var/run /usr/sbin/cron /.mongodb /etc/ssl /usr/local/share
|
&& chmod -R ugo+w /var/run /usr/sbin/cron /.mongodb /etc/ssl /usr/local/share
|
||||||
|
|
||||||
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check=/watchtower-hooks/pre-check.sh
|
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check=/watchtower-hooks/pre-check.sh
|
||||||
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update=/watchtower-hooks/pre-update.sh
|
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update=/watchtower-hooks/pre-update.sh
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public class InstanceConfig implements ApplicationListener<ApplicationReadyEvent
|
||||||
|
|
||||||
private final InstanceConfigHelper instanceConfigHelper;
|
private final InstanceConfigHelper instanceConfigHelper;
|
||||||
|
|
||||||
private static final String WWW_PATH = System.getenv("NGINX_WWW_PATH");
|
private static final String WWW_PATH = System.getenv("WWW_PATH");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
|
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
|
||||||
|
|
|
||||||
133
deploy/docker/fs/opt/appsmith/caddy-reconfigure.mjs
Normal file
133
deploy/docker/fs/opt/appsmith/caddy-reconfigure.mjs
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
import * as fs from "fs"
|
||||||
|
import {dirname} from "path"
|
||||||
|
import {spawnSync} from "child_process"
|
||||||
|
import {X509Certificate} from "crypto"
|
||||||
|
|
||||||
|
const APPSMITH_CUSTOM_DOMAIN = process.env.APPSMITH_CUSTOM_DOMAIN ?? null
|
||||||
|
const CaddyfilePath = process.env.TMP + "/Caddyfile"
|
||||||
|
|
||||||
|
let certLocation = null
|
||||||
|
if (APPSMITH_CUSTOM_DOMAIN != null) {
|
||||||
|
try {
|
||||||
|
fs.accessSync("/appsmith-stacks/ssl/fullchain.pem", fs.constants.R_OK)
|
||||||
|
certLocation = "/appsmith-stacks/ssl"
|
||||||
|
} catch (_) {
|
||||||
|
// no custom certs, see if old certbot certs are there.
|
||||||
|
const letsEncryptCertLocation = "/etc/letsencrypt/live/" + APPSMITH_CUSTOM_DOMAIN
|
||||||
|
const fullChainPath = letsEncryptCertLocation + `/fullchain.pem`
|
||||||
|
try {
|
||||||
|
fs.accessSync(fullChainPath, fs.constants.R_OK)
|
||||||
|
console.log("Old Let's Encrypt cert file exists, now checking if it's expired.")
|
||||||
|
if (!isCertExpired(fullChainPath)) {
|
||||||
|
certLocation = letsEncryptCertLocation
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// no certs there either, ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const tlsConfig = certLocation == null ? "" : `tls ${certLocation}/fullchain.pem ${certLocation}/privkey.pem`
|
||||||
|
|
||||||
|
const parts = []
|
||||||
|
|
||||||
|
parts.push(`
|
||||||
|
{
|
||||||
|
admin 127.0.0.1:2019
|
||||||
|
persist_config off
|
||||||
|
acme_ca_root /etc/ssl/certs/ca-certificates.crt
|
||||||
|
servers {
|
||||||
|
trusted_proxies static 0.0.0.0/0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(file_server) {
|
||||||
|
file_server {
|
||||||
|
precompressed br gzip
|
||||||
|
disable_canonical_uris
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(reverse_proxy) {
|
||||||
|
reverse_proxy {
|
||||||
|
to 127.0.0.1:{args[0]}
|
||||||
|
header_up -Forwarded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(all-config) {
|
||||||
|
log {
|
||||||
|
output stdout
|
||||||
|
}
|
||||||
|
skip_log /api/v1/health
|
||||||
|
|
||||||
|
header {
|
||||||
|
-Server
|
||||||
|
Content-Security-Policy "frame-ancestors ${process.env.APPSMITH_ALLOWED_FRAME_ANCESTORS ?? "'self' *"}"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
}
|
||||||
|
|
||||||
|
request_body {
|
||||||
|
max_size 150MB
|
||||||
|
}
|
||||||
|
|
||||||
|
handle {
|
||||||
|
root * {$WWW_PATH}
|
||||||
|
try_files /loading.html /index.html
|
||||||
|
import file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
root * /opt/appsmith/editor
|
||||||
|
@file file
|
||||||
|
handle @file {
|
||||||
|
import file_server
|
||||||
|
skip_log
|
||||||
|
}
|
||||||
|
error /static/* 404
|
||||||
|
|
||||||
|
handle /info {
|
||||||
|
root * /opt/appsmith
|
||||||
|
rewrite * /info.json
|
||||||
|
import file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
@backend path /api/* /oauth2/* /login/*
|
||||||
|
handle @backend {
|
||||||
|
import reverse_proxy 8080
|
||||||
|
}
|
||||||
|
|
||||||
|
handle /rts/* {
|
||||||
|
import reverse_proxy 8091
|
||||||
|
}
|
||||||
|
|
||||||
|
redir /supervisor /supervisor/
|
||||||
|
handle_path /supervisor/* {
|
||||||
|
import reverse_proxy 9001
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_errors {
|
||||||
|
respond "{err.status_code} {err.status_text}" {err.status_code}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localhost:80 127.0.0.1:80 {
|
||||||
|
import all-config
|
||||||
|
}
|
||||||
|
|
||||||
|
${APPSMITH_CUSTOM_DOMAIN || ":80"} {
|
||||||
|
import all-config
|
||||||
|
${tlsConfig}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
fs.mkdirSync(dirname(CaddyfilePath), { recursive: true })
|
||||||
|
fs.writeFileSync(CaddyfilePath, parts.join("\n"))
|
||||||
|
spawnSync("/opt/caddy/caddy", ["fmt", "--overwrite", CaddyfilePath])
|
||||||
|
spawnSync("/opt/caddy/caddy", ["reload", "--config", CaddyfilePath])
|
||||||
|
|
||||||
|
function isCertExpired(path) {
|
||||||
|
const cert = new X509Certificate(fs.readFileSync(path, "utf-8"))
|
||||||
|
console.log(path, cert)
|
||||||
|
return new Date(cert.validTo) < new Date()
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ stacks_path=/appsmith-stacks
|
||||||
export SUPERVISORD_CONF_TARGET="$TMP/supervisor-conf.d/" # export for use in supervisord.conf
|
export SUPERVISORD_CONF_TARGET="$TMP/supervisor-conf.d/" # export for use in supervisord.conf
|
||||||
export MONGODB_TMP_KEY_PATH="$TMP/mongodb-key" # export for use in supervisor process mongodb.conf
|
export MONGODB_TMP_KEY_PATH="$TMP/mongodb-key" # export for use in supervisor process mongodb.conf
|
||||||
|
|
||||||
mkdir -pv "$SUPERVISORD_CONF_TARGET" "$NGINX_WWW_PATH"
|
mkdir -pv "$SUPERVISORD_CONF_TARGET" "$WWW_PATH"
|
||||||
|
|
||||||
# ip is a reserved keyword for tracking events in Mixpanel. Instead of showing the ip as is Mixpanel provides derived properties.
|
# ip is a reserved keyword for tracking events in Mixpanel. Instead of showing the ip as is Mixpanel provides derived properties.
|
||||||
# As we want derived props alongwith the ip address we are sharing the ip address in separate keys
|
# As we want derived props alongwith the ip address we are sharing the ip address in separate keys
|
||||||
|
|
@ -347,11 +347,6 @@ configure_supervisord() {
|
||||||
cp "$supervisord_conf_source/redis.conf" "$SUPERVISORD_CONF_TARGET"
|
cp "$supervisord_conf_source/redis.conf" "$SUPERVISORD_CONF_TARGET"
|
||||||
mkdir -p "$stacks_path/data/redis"
|
mkdir -p "$stacks_path/data/redis"
|
||||||
fi
|
fi
|
||||||
if ! [[ -e "/appsmith-stacks/ssl/fullchain.pem" ]] || ! [[ -e "/appsmith-stacks/ssl/privkey.pem" ]]; then
|
|
||||||
if [[ -n "${APPSMITH_CUSTOM_DOMAIN-}" ]]; then
|
|
||||||
cp "$supervisord_conf_source/cron.conf" "$SUPERVISORD_CONF_TARGET"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [[ $runEmbeddedPostgres -eq 1 ]]; then
|
if [[ $runEmbeddedPostgres -eq 1 ]]; then
|
||||||
cp "$supervisord_conf_source/postgres.conf" "$SUPERVISORD_CONF_TARGET"
|
cp "$supervisord_conf_source/postgres.conf" "$SUPERVISORD_CONF_TARGET"
|
||||||
fi
|
fi
|
||||||
|
|
@ -443,23 +438,20 @@ init_postgres || runEmbeddedPostgres=0
|
||||||
}
|
}
|
||||||
|
|
||||||
init_loading_pages(){
|
init_loading_pages(){
|
||||||
local starting_page="/opt/appsmith/templates/appsmith_starting.html"
|
export XDG_DATA_HOME=/appsmith-stacks/data # so that caddy saves tls certs and other data under stacks/data/caddy
|
||||||
local initializing_page="/opt/appsmith/templates/appsmith_initializing.html"
|
export XDG_CONFIG_HOME=/appsmith-stacks/configuration
|
||||||
local editor_load_page="$NGINX_WWW_PATH/loading.html"
|
mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME"
|
||||||
cp "$initializing_page" "$NGINX_WWW_PATH/index.html"
|
cp templates/loading.html "$WWW_PATH"
|
||||||
# TODO: Also listen on 443, if HTTP certs are available.
|
if [[ -z "${APPSMITH_ALLOWED_FRAME_ANCESTORS-}" ]]; then
|
||||||
cat <<EOF > "$TMP/nginx-app.conf"
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
|
||||||
server {
|
export APPSMITH_ALLOWED_FRAME_ANCESTORS="'self'"
|
||||||
listen ${PORT:-80} default_server;
|
else
|
||||||
location / {
|
# Remove any extra rules that may be present in the frame ancestors value. This is to prevent this env variable from
|
||||||
try_files \$uri \$uri/ /index.html =404;
|
# being used to inject more rules to the CSP header. If needed, that should be supported/solved separately.
|
||||||
}
|
export APPSMITH_ALLOWED_FRAME_ANCESTORS="${APPSMITH_ALLOWED_FRAME_ANCESTORS%;*}"
|
||||||
}
|
fi
|
||||||
EOF
|
node caddy-reconfigure.mjs
|
||||||
# Start nginx page to display the Appsmith is Initializing page
|
/opt/caddy/caddy start --config "$TMP/Caddyfile"
|
||||||
nginx
|
|
||||||
# Update editor nginx page for starting page
|
|
||||||
cp "$starting_page" "$editor_load_page"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main Section
|
# Main Section
|
||||||
|
|
@ -498,8 +490,5 @@ mkdir -p /appsmith-stacks/data/{backup,restore}
|
||||||
# Create sub-directory to store services log in the container mounting folder
|
# Create sub-directory to store services log in the container mounting folder
|
||||||
mkdir -p /appsmith-stacks/logs/{supervisor,backend,cron,editor,rts,mongodb,redis,postgres,appsmithctl}
|
mkdir -p /appsmith-stacks/logs/{supervisor,backend,cron,editor,rts,mongodb,redis,postgres,appsmithctl}
|
||||||
|
|
||||||
# Stop nginx gracefully
|
|
||||||
nginx -s quit
|
|
||||||
|
|
||||||
# Handle CMD command
|
# Handle CMD command
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
init_ssl_cert() {
|
|
||||||
APPSMITH_CUSTOM_DOMAIN="$1"
|
|
||||||
|
|
||||||
local rsa_key_size=4096
|
|
||||||
local data_path="/appsmith-stacks/data/certificate"
|
|
||||||
|
|
||||||
mkdir -p "$data_path/www"
|
|
||||||
|
|
||||||
echo "Re-generating nginx config template with domain"
|
|
||||||
/opt/appsmith/templates/nginx-app.conf.sh "0" "$APPSMITH_CUSTOM_DOMAIN"
|
|
||||||
|
|
||||||
echo "Start Nginx to verify certificate"
|
|
||||||
nginx
|
|
||||||
|
|
||||||
local live_path="/etc/letsencrypt/live/$APPSMITH_CUSTOM_DOMAIN"
|
|
||||||
local ssl_path="/appsmith-stacks/ssl"
|
|
||||||
if [[ -e "$ssl_path/fullchain.pem" ]] && [[ -e "$ssl_path/privkey.pem" ]]; then
|
|
||||||
echo "Existing custom certificate"
|
|
||||||
echo "Stop Nginx"
|
|
||||||
nginx -s stop
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -e "$live_path" ]]; then
|
|
||||||
echo "Existing certificate for domain $APPSMITH_CUSTOM_DOMAIN"
|
|
||||||
echo "Stop Nginx"
|
|
||||||
nginx -s stop
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Creating certificate for '$APPSMITH_CUSTOM_DOMAIN'"
|
|
||||||
|
|
||||||
echo "Requesting Let's Encrypt certificate for '$APPSMITH_CUSTOM_DOMAIN'..."
|
|
||||||
echo "Generating OpenSSL key for '$APPSMITH_CUSTOM_DOMAIN'..."
|
|
||||||
|
|
||||||
mkdir -p "$live_path" && openssl req -x509 -nodes -newkey rsa:2048 -days 1 \
|
|
||||||
-keyout "$live_path/privkey.pem" \
|
|
||||||
-out "$live_path/fullchain.pem" \
|
|
||||||
-subj "/CN=localhost"
|
|
||||||
|
|
||||||
echo "Removing key now that validation is done for $APPSMITH_CUSTOM_DOMAIN..."
|
|
||||||
rm -Rfv /etc/letsencrypt/live/$APPSMITH_CUSTOM_DOMAIN /etc/letsencrypt/archive/$APPSMITH_CUSTOM_DOMAIN /etc/letsencrypt/renewal/$APPSMITH_CUSTOM_DOMAIN.conf
|
|
||||||
|
|
||||||
echo "Generating certification for domain $APPSMITH_CUSTOM_DOMAIN"
|
|
||||||
mkdir -p "$data_path/certbot"
|
|
||||||
certbot certonly --webroot --webroot-path="$data_path/certbot" \
|
|
||||||
--register-unsafely-without-email \
|
|
||||||
--domains $APPSMITH_CUSTOM_DOMAIN \
|
|
||||||
--rsa-key-size $rsa_key_size \
|
|
||||||
--agree-tos \
|
|
||||||
--force-renewal
|
|
||||||
|
|
||||||
if (($? != 0)); then
|
|
||||||
echo "Stop Nginx due to provisioning fail"
|
|
||||||
nginx -s stop
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Stop Nginx"
|
|
||||||
nginx -s stop
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import * as fs from "fs/promises";
|
|
||||||
|
|
||||||
const TMP = process.env.TMP;
|
|
||||||
const NGINX_WWW_PATH = process.env.NGINX_WWW_PATH;
|
|
||||||
|
|
||||||
async function applyNginxChanges() {
|
|
||||||
const contents = await fs.readFile("/etc/nginx/nginx.conf", "utf8")
|
|
||||||
|
|
||||||
const modContents = contents
|
|
||||||
.replace("pid /run/nginx.pid;", `pid ${TMP}/nginx.pid;`)
|
|
||||||
.replace("# server_tokens off;", "server_tokens off; more_set_headers 'Server: ';")
|
|
||||||
.replace("gzip on;", "gzip on; gzip_types *;")
|
|
||||||
.replace("include /etc/nginx/conf.d/*.conf;", [
|
|
||||||
"include /etc/nginx/conf.d/*.conf;",
|
|
||||||
`include ${TMP}/nginx-app.conf;`,
|
|
||||||
`root ${NGINX_WWW_PATH};`,
|
|
||||||
].join("\n"));
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
fs.writeFile("/etc/nginx/nginx.conf.original", contents),
|
|
||||||
fs.writeFile("/etc/nginx/nginx.conf", modContents),
|
|
||||||
fs.rm("/etc/nginx/sites-enabled", { recursive: true }),
|
|
||||||
fs.rm("/etc/nginx/conf.d", { recursive: true }),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
await applyNginxChanges();
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
ENV_PATH="/appsmith-stacks/configuration/docker.env"
|
|
||||||
PRE_DEFINED_ENV_PATH="$TMP/pre-define.env"
|
|
||||||
if [[ -f /appsmith-stacks/configuration/docker.env ]]; then
|
|
||||||
echo 'Load environment configuration'
|
|
||||||
set -o allexport
|
|
||||||
. "$ENV_PATH"
|
|
||||||
. "$PRE_DEFINED_ENV_PATH"
|
|
||||||
set +o allexport
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n $APPSMITH_CUSTOM_DOMAIN ]]; then
|
|
||||||
data_path="/appsmith-stacks/data/certificate"
|
|
||||||
domain="$APPSMITH_CUSTOM_DOMAIN"
|
|
||||||
rsa_key_size=4096
|
|
||||||
|
|
||||||
certbot certonly --webroot --webroot-path="$data_path/certbot" \
|
|
||||||
--register-unsafely-without-email \
|
|
||||||
--domains $domain \
|
|
||||||
--rsa-key-size $rsa_key_size \
|
|
||||||
--agree-tos \
|
|
||||||
--force-renewal
|
|
||||||
supervisorctl restart editor
|
|
||||||
else
|
|
||||||
echo 'Custom domain not configured. Cannot enable SSL without a custom domain.' >&2
|
|
||||||
fi
|
|
||||||
35
deploy/docker/fs/opt/appsmith/run-caddy.sh
Executable file
35
deploy/docker/fs/opt/appsmith/run-caddy.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
if [[ -z "${APPSMITH_DISABLE_IFRAME_WIDGET_SANDBOX-}" ]]; then
|
||||||
|
# For backwards compatibility, if this is not set to anything, we default to no sandbox for iframe widgets.
|
||||||
|
export APPSMITH_DISABLE_IFRAME_WIDGET_SANDBOX="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
apply-env-vars() {
|
||||||
|
original="$1"
|
||||||
|
served="$2"
|
||||||
|
node -e '
|
||||||
|
const fs = require("fs")
|
||||||
|
const content = fs.readFileSync("'"$original"'", "utf8").replace(
|
||||||
|
/\b__(APPSMITH_[A-Z0-9_]+)__\b/g,
|
||||||
|
(placeholder, name) => (process.env[name] || "")
|
||||||
|
)
|
||||||
|
fs.writeFileSync("'"$served"'", content)
|
||||||
|
'
|
||||||
|
pushd "$(dirname "$served")"
|
||||||
|
gzip --keep --force "$(basename "$served")"
|
||||||
|
popd
|
||||||
|
}
|
||||||
|
|
||||||
|
apply-env-vars /opt/appsmith/editor/index.html "$WWW_PATH/index.html"
|
||||||
|
|
||||||
|
node caddy-reconfigure.mjs
|
||||||
|
|
||||||
|
# Caddy may already be running for the loading page.
|
||||||
|
/opt/caddy/caddy stop --config "$TMP/Caddyfile" || true
|
||||||
|
|
||||||
|
exec /opt/caddy/caddy run --config "$TMP/Caddyfile"
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o nounset
|
|
||||||
set -o pipefail
|
|
||||||
set -o xtrace
|
|
||||||
|
|
||||||
ssl_conf_path="/appsmith-stacks/data/certificate/conf"
|
|
||||||
|
|
||||||
mkdir -pv "$ssl_conf_path"
|
|
||||||
|
|
||||||
cat <<EOF > "$ssl_conf_path/options-ssl-nginx.conf"
|
|
||||||
# This file contains important security parameters. If you modify this file
|
|
||||||
# manually, Certbot will be unable to automatically provide future security
|
|
||||||
# updates. Instead, Certbot will print and log an error message with a path to
|
|
||||||
# the up-to-date file that you will need to refer to when manually updating
|
|
||||||
# this file.
|
|
||||||
|
|
||||||
ssl_session_cache shared:le_nginx_SSL:10m;
|
|
||||||
ssl_session_timeout 1440m;
|
|
||||||
ssl_session_tickets off;
|
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_prefer_server_ciphers off;
|
|
||||||
|
|
||||||
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF > "$ssl_conf_path/ssl-dhparams.pem"
|
|
||||||
-----BEGIN DH PARAMETERS-----
|
|
||||||
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
|
|
||||||
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
|
|
||||||
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
|
|
||||||
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
|
|
||||||
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
|
|
||||||
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
|
|
||||||
-----END DH PARAMETERS-----
|
|
||||||
EOF
|
|
||||||
|
|
||||||
if [[ -z "${APPSMITH_ALLOWED_FRAME_ANCESTORS-}" ]]; then
|
|
||||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
|
|
||||||
export APPSMITH_ALLOWED_FRAME_ANCESTORS="'self'"
|
|
||||||
else
|
|
||||||
# Remove any extra rules that may be present in the frame ancestors value. This is to prevent this env variable from
|
|
||||||
# being used to inject more rules to the CSP header. If needed, that should be supported/solved separately.
|
|
||||||
export APPSMITH_ALLOWED_FRAME_ANCESTORS="${APPSMITH_ALLOWED_FRAME_ANCESTORS%;*}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${APPSMITH_DISABLE_IFRAME_WIDGET_SANDBOX-}" ]]; then
|
|
||||||
# For backwards compatibility, if this is not set to anything, we default to no sandbox for iframe widgets.
|
|
||||||
export APPSMITH_DISABLE_IFRAME_WIDGET_SANDBOX="true"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check exist certificate with given custom domain
|
|
||||||
# Heroku not support for custom domain, only generate HTTP config if deploying on Heroku
|
|
||||||
use_https=0
|
|
||||||
if [[ -n ${APPSMITH_CUSTOM_DOMAIN-} ]] && [[ -z ${DYNO-} ]]; then
|
|
||||||
use_https=1
|
|
||||||
if ! [[ -e "/etc/letsencrypt/live/$APPSMITH_CUSTOM_DOMAIN" ]]; then
|
|
||||||
source "/opt/appsmith/init_ssl_cert.sh"
|
|
||||||
if ! init_ssl_cert "$APPSMITH_CUSTOM_DOMAIN"; then
|
|
||||||
echo "Status code from init_ssl_cert is $?"
|
|
||||||
use_https=0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
/opt/appsmith/templates/nginx-app.conf.sh "$use_https" "${APPSMITH_CUSTOM_DOMAIN-}"
|
|
||||||
|
|
||||||
cp -r /opt/appsmith/editor/* "$NGINX_WWW_PATH"
|
|
||||||
|
|
||||||
apply-env-vars() {
|
|
||||||
original="$1"
|
|
||||||
served="$2"
|
|
||||||
node -e '
|
|
||||||
const fs = require("fs")
|
|
||||||
const content = fs.readFileSync("'"$original"'", "utf8").replace(
|
|
||||||
/\b__(APPSMITH_[A-Z0-9_]+)__\b/g,
|
|
||||||
(placeholder, name) => (process.env[name] || "")
|
|
||||||
)
|
|
||||||
fs.writeFileSync("'"$served"'", content)
|
|
||||||
'
|
|
||||||
pushd "$(dirname "$served")"
|
|
||||||
gzip --keep --force "$(basename "$served")"
|
|
||||||
popd
|
|
||||||
}
|
|
||||||
|
|
||||||
apply-env-vars /opt/appsmith/editor/index.html "$NGINX_WWW_PATH/index.html"
|
|
||||||
|
|
||||||
exec nginx -g "daemon off;error_log stderr info;"
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
python3 /opt/appsmith/starting-page-init.py
|
python3 /opt/appsmith/starting-page-init.py
|
||||||
rm -f "$NGINX_WWW_PATH/loading.html"
|
rm -f "$WWW_PATH/loading.html"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import urllib.request
|
||||||
|
|
||||||
|
|
||||||
LOADING_TEMPLATE_PAGE = r'/opt/appsmith/templates/appsmith_starting.html'
|
LOADING_TEMPLATE_PAGE = r'/opt/appsmith/templates/appsmith_starting.html'
|
||||||
LOADING_PAGE_EDITOR = os.getenv("NGINX_WWW_PATH") + '/loading.html'
|
LOADING_PAGE_EDITOR = os.getenv("WWW_PATH") + '/loading.html'
|
||||||
BACKEND_HEALTH_ENDPOINT = "http://localhost:8080/api/v1/health"
|
BACKEND_HEALTH_ENDPOINT = "http://localhost:8080/api/v1/health"
|
||||||
LOG_FILE = r'/appsmith-stacks/logs/backend/starting_page_init.log'
|
LOG_FILE = r'/appsmith-stacks/logs/backend/starting_page_init.log'
|
||||||
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" http-equiv="refresh" content="5"/>
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
href="https://assets.appsmith.com/appsmith-favicon-orange.ico"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
|
||||||
/>
|
|
||||||
<title>Appsmith</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="main">
|
|
||||||
<p class="bold-text">
|
|
||||||
Please wait
|
|
||||||
</p>
|
|
||||||
<p>Appsmith is initializing. This might take a few minutes.</p>
|
|
||||||
<div class="loader"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body,
|
|
||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
overflow-x: hidden;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 0;
|
|
||||||
color: #182026;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
|
||||||
Ubuntu, Cantarell, Open Sans, Helvetica Neue, Icons16, sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
letter-spacing: 0;
|
|
||||||
}
|
|
||||||
.main {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 90%;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 5%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.bold-text {
|
|
||||||
font-family: system-ui;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 24px;
|
|
||||||
margin: 24px 0 0;
|
|
||||||
}
|
|
||||||
.body-text {
|
|
||||||
margin: 8px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Safari */
|
|
||||||
@-webkit-keyframes spin {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
border: 4px solid #f3f3f3;
|
|
||||||
border-radius: 50%;
|
|
||||||
border-top: 4px solid #939090;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
-webkit-animation: spin 1s linear infinite; /* Safari */
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -14,10 +14,8 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<p class="bold-text">
|
<p class="bold-text">Appsmith is starting.</p>
|
||||||
Appsmith is starting.
|
<p>Please wait until Appsmith is ready. This may take a few minutes. </p>
|
||||||
</p>
|
|
||||||
<p>Please wait until Appsmith is ready. This process usually takes a minute. </p>
|
|
||||||
<div class="loader"></div>
|
<div class="loader"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
use_https="$1"
|
|
||||||
custom_domain="${2:-_}"
|
|
||||||
|
|
||||||
if [[ $use_https == 1 ]]; then
|
|
||||||
# By default, container will use the auto-generate certificate by Let's Encrypt
|
|
||||||
ssl_cert_path="/etc/letsencrypt/live/$custom_domain/fullchain.pem"
|
|
||||||
ssl_key_path="/etc/letsencrypt/live/$custom_domain/privkey.pem"
|
|
||||||
|
|
||||||
# In case of existing custom certificate, container will use them to configure SSL
|
|
||||||
if [[ -e "/appsmith-stacks/ssl/fullchain.pem" ]] && [[ -e "/appsmith-stacks/ssl/privkey.pem" ]]; then
|
|
||||||
ssl_cert_path="/appsmith-stacks/ssl/fullchain.pem"
|
|
||||||
ssl_key_path="/appsmith-stacks/ssl/privkey.pem"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
additional_downstream_headers='
|
|
||||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
|
||||||
add_header X-Content-Type-Options "nosniff";
|
|
||||||
'
|
|
||||||
|
|
||||||
cat <<EOF > "$TMP/nginx-app.conf"
|
|
||||||
map \$http_x_forwarded_proto \$origin_scheme {
|
|
||||||
default \$http_x_forwarded_proto;
|
|
||||||
'' \$scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
map \$http_x_forwarded_host \$origin_host {
|
|
||||||
default \$http_x_forwarded_host;
|
|
||||||
'' \$host;
|
|
||||||
}
|
|
||||||
|
|
||||||
map \$http_forwarded \$final_forwarded {
|
|
||||||
default '\$http_forwarded, host=\$host;proto=\$scheme';
|
|
||||||
'' '';
|
|
||||||
}
|
|
||||||
|
|
||||||
# redirect log to stdout for supervisor to capture
|
|
||||||
access_log /dev/stdout;
|
|
||||||
|
|
||||||
server {
|
|
||||||
|
|
||||||
$(
|
|
||||||
if [[ $use_https == 1 ]]; then
|
|
||||||
echo "
|
|
||||||
listen 80;
|
|
||||||
server_name $custom_domain;
|
|
||||||
|
|
||||||
location /.well-known/acme-challenge/ {
|
|
||||||
root /appsmith-stacks/data/certificate/certbot;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
return 301 https://\$host\$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
server_name _;
|
|
||||||
ssl_certificate $ssl_cert_path;
|
|
||||||
ssl_certificate_key $ssl_key_path;
|
|
||||||
include /appsmith-stacks/data/certificate/conf/options-ssl-nginx.conf;
|
|
||||||
ssl_dhparam /appsmith-stacks/data/certificate/conf/ssl-dhparams.pem;
|
|
||||||
"
|
|
||||||
else
|
|
||||||
echo "
|
|
||||||
listen ${PORT:-80} default_server;
|
|
||||||
server_name $custom_domain;
|
|
||||||
"
|
|
||||||
fi
|
|
||||||
)
|
|
||||||
|
|
||||||
client_max_body_size 150m;
|
|
||||||
|
|
||||||
index index.html;
|
|
||||||
error_page 404 /;
|
|
||||||
|
|
||||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
|
|
||||||
add_header Content-Security-Policy "frame-ancestors ${APPSMITH_ALLOWED_FRAME_ANCESTORS-'self' *}";
|
|
||||||
|
|
||||||
$additional_downstream_headers
|
|
||||||
|
|
||||||
location /.well-known/acme-challenge/ {
|
|
||||||
root /appsmith-stacks/data/certificate/certbot;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /supervisor {
|
|
||||||
return 301 /supervisor/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /supervisor/ {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_max_temp_file_size 0;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_set_header Host \$http_host/supervisor/;
|
|
||||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto \$origin_scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host \$origin_host;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_pass http://localhost:9001/;
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_set_header X-Forwarded-Proto \$origin_scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host \$origin_host;
|
|
||||||
proxy_set_header Forwarded \$final_forwarded;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files /loading.html \$uri /index.html =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /info {
|
|
||||||
default_type application/json;
|
|
||||||
alias /opt/appsmith/info.json;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/static/(js|css|media)\b {
|
|
||||||
# Files in these folders are hashed, so we can set a long cache time.
|
|
||||||
add_header Cache-Control "max-age=31104000, immutable"; # 360 days
|
|
||||||
$additional_downstream_headers
|
|
||||||
access_log off;
|
|
||||||
}
|
|
||||||
|
|
||||||
# If the path has an extension at the end, then respond with 404 status if the file not found.
|
|
||||||
location ~ ^/(?!supervisor/).*\.[a-z]+$ {
|
|
||||||
try_files \$uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /api {
|
|
||||||
proxy_read_timeout ${APPSMITH_SERVER_TIMEOUT:-60};
|
|
||||||
proxy_send_timeout ${APPSMITH_SERVER_TIMEOUT:-60};
|
|
||||||
proxy_pass http://localhost:8080;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /oauth2 {
|
|
||||||
proxy_pass http://localhost:8080;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /login {
|
|
||||||
proxy_pass http://localhost:8080;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /rts {
|
|
||||||
proxy_pass http://localhost:${APPSMITH_RTS_PORT:-8091};
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host \$host;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Upgrade \$http_upgrade;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[program:editor]
|
[program:editor]
|
||||||
command=/opt/appsmith/run-with-env.sh /opt/appsmith/run-nginx.sh
|
command=/opt/appsmith/run-with-env.sh /opt/appsmith/run-caddy.sh
|
||||||
priority=25
|
priority=25
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user