diff --git a/README.md b/README.md index 903e243d..9215f5de 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ I won't go in to too much detail here but here are the basics for someone new to version: '3' services: app: - image: 'jc21/nginx-proxy-manager:latest' + image: 'baudneo/nginx-proxy-manager:latest' restart: unless-stopped ports: - '80:80' @@ -100,6 +100,127 @@ Password: changeme Immediately after logging in with this default user you will be asked to modify your details and change your password. +# Timezone +## Environment Variables +- `TZ` - Set to your timezone. Example: `TZ=America/Chicago` + +## Configuration +- Instead of setting `TZ` you can mount `/etc/localtime` into the docker container +------- +# CrowdSec OpenResty Bouncer + +## NOTE +- If you don't see the bouncer hitting your local API, send a request to one of the proxied hosts in NPM. I have noticed the bouncer does not start until NPM gets a request once it is all loaded up. +- To check if the bouncer is running, use `docker logs --follow [name of your NPM container]`. There will be a log line like -> `nginx: [alert] [lua] init_by_lua:8: [Crowdsec] Initialisation done` + +## Environment Variables +- `CROWDSEC_BOUNCER=1` - Enable CrowdSec OpenResty bouncer, still needs to be configured. +- `CROWDSEC_LAPI=[URL]` - configure CrowdSec local API URL +- `CROWDSEC_KEY=[API KEY]` - configure CrowdSec API key +- `$CROWDSEC_RECAP_SECRET=[SECRET KEY]` - configure reCAPTCHA +- `$CROWDSEC_RECAP_SITE=[SITE KEY]` - configure reCAPTCHA +- `SSL_CERTS_PATH` - CA certificate used to communicate with Google for reCAPTCHA validation +## Configuration +- Config file located at `data/crowdsec/crowdsec-openresty-bouncer.conf` +- HTML templates are located at `/crowdsec/templates/` inside the container +- The first time the container is run, a crowdsec config file is created with ENV vars substituted out. User is responsible for config after first creation of the file. +- Set the URL and API key at a minimum. reCAPTCHA's vars if wanted. +------- +# Admin dashboard logging / OpenResty DEBUG level logging + +## Environment Variables +- `ADMIN_DASHBOARD_LOG=1` - Enable admin (Port 81) dashboard logging +- `OPENRESTY_DEBUG=1` - Enable DEBUG level logging for the default OpenResty `ERROR` log + +## Configuration +- Admin panel logs are located at `data/logs/admin-panel_access.log` and `data/logs/admin-panel_error.log` +- OpenResty default logs `fallback_access.log` and `fallback_error.log`. `DEBUG` level will be set on the error log, it is set to `WARN` by default. +------- +# ModSecurity +_ModSecurity WAF is installed and loaded by default, OWASP-CoreRuleSet is installed and used as the default rule set. The user is responsible for configuring modsecurity via config/CLI._ + +## Environment Variables +- `MODSEC_CREATE=1` - Force recreating the default modsecurity config, _This should never be needed_ +- `MODSEC_ADMIN_PANEL=1` - Enable ModSec for the admin panel +- `MODSEC_ENABLE=1` - Enable ModSec for the default.conf server block + +## Tips to enable +### The minimum directives that need to be added to enable modsec. +- See all directives -> https://github.com/SpiderLabs/ModSecurity-nginx#usage +``` +modsecurity on; +modsecurity_rules_file /etc/nginx/modsec/main.conf; +``` +- To enable modsec for **ALL HTTP** hosts, add the directives to `data/nginx/custom/http_top.conf` +- To enable for only **certain HTTP** hosts, add the directives to the `Advanced` tab configuration at the root level (not inside a `location` block) +- To enable only for **certain locations** on _certain HTTP_ hosts, place the directives into a `location` block inside the `Advanced` tab +- **Stream hosts are untested and, as far as I know, unsupported**. ModSec directives go in server and HTTP blocks. + +## Configuration +- By default, the audit log is enabled and is located at `data/logs/modsec_audit.log` +- The config and rule set are located at `data/modsec` and `data/modsec/ruleset` +- `data/modsec/modsecurity.conf` is the main modsec config file. +- `data/modsec/main.conf` is the main rules file, it has `Include` directives that load the actual rules +- `data/modsec` is symbolically linked to `/etc/nginx/modsec` +------- +# docker-compose.yaml +``` +version: "3" +services: + npm: + #image: 'jc21/nginx-proxy-manager:latest' + image: 'baudneo/nginx-proxy-manager:latest' + restart: always + container_name: npm-crowdsec + ports: + # Public HTTP Port: + - '80:80' + # Public HTTPS Port: + - '443:443' + # Admin Web Port: + - '81:81' + environment: + # This is the default cert used to validate reCAPTCHA + SSL_CERTS_PATH: "/etc/ssl/certs/GTS_Root_R1.pem" + TZ: "America/Chicago" + ADMIN_PANEL_LOG: "1" + CROWDSEC_BOUNCER: "1" + OPENRESTY_DEBUG: "0" + + CROWDSEC_LAPI: "http://IP TO CROWDSEC LOCAL API:8080" + CROWDSEC_KEY: "xxxxxxxxxxxxxxxxxxxxxxxx" + CROWDSEC_RECAP_SECRET: "XXXX" + CROWDSEC_RECAP_SITE: "XXXX" + # These are the settings to access your db + DB_MYSQL_HOST: "db" + DB_MYSQL_PORT: 3306 + DB_MYSQL_USER: "npm-user" + DB_MYSQL_PASSWORD: "db user password" + DB_MYSQL_NAME: "npm" + # If you would rather use Sqlite uncomment this + # and remove all DB_MYSQL_* lines above + # DB_SQLITE_FILE: "/data/database.sqlite" + # Uncomment this if IPv6 is not enabled on your host + # DISABLE_IPV6: 'true' + volumes: + - ./data:/data + - ./letsencrypt:/etc/letsencrypt + depends_on: + - db + db: + image: 'jc21/mariadb-aria:latest' + restart: always + container_name: npm_db + environment: + MYSQL_ROOT_PASSWORD: 'xxXXxxXXXxxxXXX' + MYSQL_DATABASE: 'npm' + MYSQL_USER: 'npm-user' + MYSQL_PASSWORD: "db user password" + volumes: + - ./data/mysql:/var/lib/mysql +``` + + ## Contributors diff --git a/backend/package-lock.json b/backend/package-lock.json index c48f5aa4..e88a2bbd 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -17,14 +17,14 @@ "compression": "^1.7.4", "config": "^3.3.1", "express": "^4.17.1", - "express-fileupload": "^1.1.9", + "express-fileupload": "^1.1.10", "gravatar": "^1.8.0", "json-schema-ref-parser": "^8.0.0", "jsonwebtoken": "^8.5.1", "knex": "^0.20.13", "liquidjs": "^9.11.10", "lodash": "^4.17.21", - "moment": "^2.24.0", + "moment": "^2.29.4", "mysql": "^2.18.1", "node-rsa": "^1.0.8", "nodemon": "^2.0.2", @@ -432,9 +432,9 @@ } }, "node_modules/async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, "node_modules/atob": { "version": "2.1.2", @@ -736,14 +736,14 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "node_modules/busboy": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", - "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { - "dicer": "0.3.0" + "streamsearch": "^1.1.0" }, "engines": { - "node": ">=4.5.0" + "node": ">=10.16.0" } }, "node_modules/bytes": { @@ -1428,17 +1428,6 @@ "node": ">=0.10" } }, - "node_modules/dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", - "dependencies": { - "streamsearch": "0.1.2" - }, - "engines": { - "node": ">=4.5.0" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1916,15 +1905,14 @@ } }, "node_modules/express-fileupload": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.9.tgz", - "integrity": "sha512-f2w0aoe7lj3NeD8a4MXmYQsqir3Z66I08l9AKq04QbFUAjeZNmPwTlR5Lx2NGwSu/PslsAjGC38MWzo5tTjoBg==", - "deprecated": "Please upgrade express-fileupload to version 1.1.10+ due to a security vulnerability with the parseNested option", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.4.0.tgz", + "integrity": "sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==", "dependencies": { - "busboy": "^0.3.1" + "busboy": "^1.6.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" } }, "node_modules/extend": { @@ -3624,9 +3612,9 @@ } }, "node_modules/moment": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", - "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", "engines": { "node": "*" } @@ -5297,11 +5285,11 @@ } }, "node_modules/streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", "engines": { - "node": ">=0.8.0" + "node": ">=10.0.0" } }, "node_modules/string_decoder": { @@ -6625,9 +6613,9 @@ "dev": true }, "async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, "atob": { "version": "2.1.2", @@ -6846,11 +6834,11 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "busboy": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", - "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "requires": { - "dicer": "0.3.0" + "streamsearch": "^1.1.0" } }, "bytes": { @@ -7387,14 +7375,6 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==" }, - "dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", - "requires": { - "streamsearch": "0.1.2" - } - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -7773,11 +7753,11 @@ } }, "express-fileupload": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.9.tgz", - "integrity": "sha512-f2w0aoe7lj3NeD8a4MXmYQsqir3Z66I08l9AKq04QbFUAjeZNmPwTlR5Lx2NGwSu/PslsAjGC38MWzo5tTjoBg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.4.0.tgz", + "integrity": "sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==", "requires": { - "busboy": "^0.3.1" + "busboy": "^1.6.0" } }, "extend": { @@ -9075,9 +9055,9 @@ } }, "moment": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", - "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "ms": { "version": "2.0.0", @@ -10375,9 +10355,9 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" }, "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" }, "string_decoder": { "version": "1.3.0", diff --git a/docker/Dockerfile b/docker/Dockerfile index 10ed69d0..88f0b6e8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,6 +10,7 @@ ARG BUILD_VERSION ARG BUILD_COMMIT ARG BUILD_DATE ARG BASE_TAG +ARG SSL_CERTS_PATH ENV SUPPRESS_NO_CONFIG_WARNING=1 \ S6_FIX_ATTRS_HIDDEN=1 \ @@ -21,7 +22,12 @@ ENV SUPPRESS_NO_CONFIG_WARNING=1 \ OPENRESTY_DEBUG="0" \ MODSEC_CREATE="0" \ MODSEC_ENABLE="0" \ - MODSEC_ADMIN_PANEL="0" + MODSEC_ADMIN_PANEL="0" \ + CROWDSEC_UPDATE_DIR='/cs-update' \ + GEOLITE_DB_GRAB="0" \ + GEOLITE2_DB_GRAB="0" \ + GEOIP_DIR="/geoip_db" \ + SSL_CERTS_PATH="${SSL_CERTS_PATH:-'/etc/ssl/certs/GTS_Root_R1.pem'}" RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ && apt-get update \ diff --git a/docker/rootfs/etc/cont-init.d/90_mmdb.sh b/docker/rootfs/etc/cont-init.d/90_mmdb.sh index 2648239c..fc367ac4 100644 --- a/docker/rootfs/etc/cont-init.d/90_mmdb.sh +++ b/docker/rootfs/etc/cont-init.d/90_mmdb.sh @@ -8,13 +8,15 @@ log() { if [[ -n "${GEOLITE2_DB_GRAB}" ]]; then if [[ "${GEOLITE2_DB_GRAB}" == "1" ]] || [[ "${GEOLITE2_DB_GRAB}" -eq 1 ]]; then log "GeoLite2 DB Grab configured, installing/updating GeoLite2 Database's" - geo2="${GEOIP_DIR:-/geoip}/2" + geo2="${GEOIP_DIR:/geoip_db}/2" mkdir -p "$geo2/tmp" - GEOIP2_DB_URLS=( - "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb" - "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb" - "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-ASN.mmdb" - ) + if [ -z "$GEOIP2_DB_URLS" ]; then + GEOIP2_DB_URLS=( + "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb" + "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb" + "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-ASN.mmdb" + ) + fi # download new dbs and diff them, update if different for db in "${GEOIP2_DB_URLS[@]}"; do log "Downloading ${db##*/} from ${db%/*}..." @@ -46,7 +48,7 @@ if [[ -n "${GEOLITE_DB_GRAB}" ]]; then if [ "${GEOLITE_DB_GRAB}" == "1" ] || [ "${GEOLITE2_DB_GRAB}" -eq 1 ]; then log "GeoLite LEGACY DB Grab configured, downloading GeoLite LEGACY Database's" - geo1="${GEOIP_DIR:-/geoip}/1" + geo1="${GEOIP_DIR:/geoip_db}/1" mkdir -p "$geo1" diff --git a/docker/rootfs/etc/cont-init.d/99_crowdsec-openresty-bouncer.sh b/docker/rootfs/etc/cont-init.d/99_crowdsec-openresty-bouncer.sh index 59b0db8b..fea1643c 100755 --- a/docker/rootfs/etc/cont-init.d/99_crowdsec-openresty-bouncer.sh +++ b/docker/rootfs/etc/cont-init.d/99_crowdsec-openresty-bouncer.sh @@ -17,10 +17,13 @@ if [ "${CROWDSEC_BOUNCER}" == "1" ] || [ "${CROWDSEC_BOUNCER}" -eq 1 ]; then log "Crowdsec OpenResty Bouncer Config copied to /data/crowdsec/crowdsec-openresty-bouncer.conf" fi # Create lualib plugin directory for crowdsec and move crowdsec lua libs into it + log "Creating CrowdSec lualib directories in /etc/nginx" mkdir -p /etc/nginx/lualib/plugins/crowdsec/ + log "Copying CrowdSec Lua libraries to /etc/nginx/lualib/plugins/crowdsec/" cp -r /crowdsec/lua/lib/* /etc/nginx/lualib/ # This initilizes crowdsec as /etc/nginx/conf.d/* is included in nginx.conf # Fixes -> SSL_CTX_load_verify_locations("/etc/nginx/${SSL_CERTS_PATH}") failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/${SSL_CERTS_PATH}','r') error:2006D080:BIO routines:BIO_new_file:no such file error:0B084002:x509 certificate routines:X509_load_cert_crl_file:system lib) + log "envsubst \${SSL_CERTS_PATH} (${SSL_CERTS_PATH}) in crowdsec_openresty.conf" SSL_CERTS_PATH=${SSL_CERTS_PATH} envsubst < /crowdsec/crowdsec_openresty.conf > /etc/nginx/conf.d/crowdsec_openresty.conf # cp /crowdsec/crowdsec_openresty.conf /etc/nginx/conf.d/ else diff --git a/local-build.sh b/local-build.sh index a5fb7d94..8834d363 100755 --- a/local-build.sh +++ b/local-build.sh @@ -12,15 +12,15 @@ cd "${DIR}" export DOCKER_IMAGE=baudneo/nginx-proxy-manager export MAINTAINER="baudneo " export REPO_OWNER="baudneo" -export BASE_TAG='latest' +export BASE_TAG='local_latest' export TARGETPLATFORM=amd64 export BUILD_VERSION=dev export BUILD_COMMIT= export BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" export SSL_CERTS_PATH="/etc/ssl/certs/GTS_Root_R1.pem" -echo -e "${YELLOW}❯❯❯ ${CYAN}Running ${RED}'scripts/frontend-build'${RESET}" -bash ./scripts/frontend-build +#echo -e "${YELLOW}❯❯❯ ${CYAN}Running ${RED}'scripts/frontend-build'${RESET}" +#bash ./scripts/frontend-build # Build echo -e "${BLUE}❯ ${CYAN}Building Image [${DOCKER_IMAGE}] with tag: ${YELLOW}${BASE_TAG}${CYAN}...${RESET}" docker build \