From 3ae49a8f40d03ec0b68e3131fa335dd4fac176dd Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Mon, 21 Oct 2024 13:19:59 -0400 Subject: [PATCH 01/12] Create .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23b7ea2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Ignore mkcert generated files (certificates and keys) +localhost+*.pem From 913c5bac405bcd17d95c36ade17c312c7e6fda57 Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Mon, 21 Oct 2024 13:20:03 -0400 Subject: [PATCH 02/12] Update README.md --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 41713d3..75958c6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ -# Nginx/LetsEncrypt Reverse Proxy -The goal of this repository is to make it easy to [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) one or more website services on a Virtual Private Server (VPS). **Note: Services must be started with Docker.** - -[Here](https://github.com/MattHalloran/NLN) is a project that uses this. +# Nginx Reverse Proxy with SSL Certificate +The goal of this repository is to make it easy to set up a [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) and [SSL certificate](https://www.cloudflare.com/learning/ssl/what-is-an-ssl-certificate/) for a website running locally or on a VPS. When running locally, the SSL certificate is self-signed. When running on a VPS, the SSL certificate is provided by [LetsEncrypt](https://letsencrypt.org/). Heavily inspired by [this article](https://olex.biz/2019/09/hosting-with-docker-nginx-reverse-proxy-letsencrypt/). If you're looking for someone to thank, it is them! @@ -14,18 +12,28 @@ Heavily inspired by [this article](https://olex.biz/2019/09/hosting-with-docker- | [Docker](https://www.docker.com/) | Container handler | latest | ## Prerequisites -1. Must have a website name, with access to its DNS settings. If you're not sure where to get started, I like using [Google Domains](https://domains.google/). -2. Must have access to a Virtual Private Server (VPS). They can be as little as $5 a month. Here are some good sites: +1. If not running locally, must have a website name and access to its DNS settings +2. If not running locally, must have access to a Virtual Private Server (VPS). Here are some good sites: * [DigitalOcean](https://m.do.co/c/eb48adcdd2cb) (Referral link) * [Vultr](https://www.vultr.com/) * [Linode](https://www.linode.com/) -3. Must have Dockerfiles or docker-compose files to start your website's services. Each service that interfaces with Nginx (i.e. is connected to with a port) can be configured with the following environment variables: +3. Must have Dockerfiles or docker-compose files to start your website's services. Each service that interfaces with Nginx (i.e. is connected to with a port) can be configured using the following environment variables: - *VIRTUAL_HOST* - the website's name(s), separated by a comma with no spaces (e.g. `examplesite.com,www.examplesite.com`) - *VIRTUAL_PORT* - the container's port - *LETSENCRYPT_HOST* - website name used by LetsEncrypt. Most likely the same as *VIRTUAL_HOST* - *LETSENCRYPT_EMAIL* - the email address to be associated with the LetsEncrypt process ## Getting started + +### Running locally +1. Clone repository: + `git clone https://github.com/MattHalloran/NginxSSLReverseProxy && cd NginxSSLReverseProxy` +2. Run setup script: + `chmod +x ./scripts/fullSetup.sh && ./scripts/fullSetup.sh` +3. Start docker: + a. `sudo docker-compose-local.yml up -d` (note which `.yml` file we're using) + +### Running on a VPS 1. Set up VPS ([example](https://www.youtube.com/watch?v=Dwlqa6NJdMo&t=142s)). 2. Edit DNS settings to point to the VPS. Here is an example: | Host Name | Type | TTL | Data | @@ -39,7 +47,7 @@ Heavily inspired by [this article](https://olex.biz/2019/09/hosting-with-docker- 5. Run setup script: `chmod +x ./scripts/fullSetup.sh && ./scripts/fullSetup.sh` 6. Start docker: - `sudo docker-compose up -d` + a. `sudo docker-compose up -d` (note which `.yml` file we're using) ## Common commands From 48ff45c721e2b7eeae10e988fe4091b1e9c2186d Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Mon, 21 Oct 2024 13:20:21 -0400 Subject: [PATCH 03/12] `prettify.sh` -> `utils.sh` --- scripts/fullSetup.sh | 2 +- scripts/prettify.sh | 44 ----------------- scripts/utils.sh | 112 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 45 deletions(-) delete mode 100644 scripts/prettify.sh create mode 100644 scripts/utils.sh diff --git a/scripts/fullSetup.sh b/scripts/fullSetup.sh index 5d791f2..6356486 100644 --- a/scripts/fullSetup.sh +++ b/scripts/fullSetup.sh @@ -1,7 +1,6 @@ #!/bin/bash # Fully sets up server HERE=`dirname $0` -source "${HERE}/prettify.sh" # ======================================================== # General Ubuntu setup @@ -94,3 +93,4 @@ sudo ufw allow 80/tcp sudo ufw allow ssh sudo sysctl -p +source "${HERE}/utils.sh" diff --git a/scripts/prettify.sh b/scripts/prettify.sh deleted file mode 100644 index 8ead37b..0000000 --- a/scripts/prettify.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# These functions help to prettify echos - -# Determine if tput is available -if [ -n "$(command -v tput)" ]; then - # Set colors - RED=$(tput setaf 1) - GREEN=$(tput setaf 2) - YELLOW=$(tput setaf 3) - BLUE=$(tput setaf 4) - MAGENTA=$(tput setaf 5) - CYAN=$(tput setaf 6) - WHITE=$(tput setaf 7) - RESET=$(tput sgr0) -else - RED="" - GREEN="" - YELLOW="" - BLUE="" - MAGENTA="" - CYAN="" - WHITE="" - RESET="" -fi - -# Print header message -header() { - echo "${MAGENTA}${1}${RESET}" -} - -# Print info message -info() { - echo "${CYAN}${1}${RESET}" -} - -# Print success message -success() { - echo "${GREEN}${1}${RESET}" -} - -# Print error message -error() { - echo "${RED}${1}${RESET}" -} \ No newline at end of file diff --git a/scripts/utils.sh b/scripts/utils.sh new file mode 100644 index 0000000..3b67ca2 --- /dev/null +++ b/scripts/utils.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# Exit codes +E_NO_TPUT=1 + +# Set default terminal type if not set +export TERM=${TERM:-xterm} + +# Helper function to get color code +get_color_code() { + local color=$1 + case $color in + RED) echo "1" ;; + GREEN) echo "2" ;; + YELLOW) echo "3" ;; + BLUE) echo "4" ;; + MAGENTA) echo "5" ;; + CYAN) echo "6" ;; + WHITE) echo "7" ;; + *) echo "0" ;; + esac +} + +# Initialize a single color +initialize_color() { + local color_name="$1" + local color_code=$(get_color_code "$color_name") + + if [ -t 1 ] && command -v tput >/dev/null 2>&1; then + eval "$color_name=$(tput setaf "$color_code")" + else + eval "$color_name=''" + fi +} + +# Initialize color reset +initialize_reset() { + if [ -t 1 ] && command -v tput >/dev/null 2>&1; then + RESET=$(tput sgr0) + else + RESET='' + fi +} + +# Echo colored text +echo_color() { + local color="$1" + local message="$2" + + initialize_color "$color" + initialize_reset + echo "${!color}${message}${RESET}" +} + +# Print header message +header() { + echo_color MAGENTA "$1" +} + +# Print info message +info() { + echo_color CYAN "$1" +} + +# Print success message +success() { + echo_color GREEN "$1" +} + +# Print error message +error() { + echo_color RED "$1" +} + +# Print warning message +warning() { + echo_color YELLOW "$1" +} + +# Print input prompt message +prompt() { + echo_color BLUE "$1" +} + +# One-line confirmation prompt +prompt_confirm() { + local message="$1" + prompt "$message (y/n) " + read -r -n 1 confirm + echo + case "$confirm" in + [Yy]*) return 0 ;; # User confirmed + *) return 1 ;; # User did not confirm + esac +} + +# Exit with error message and code +exit_with_error() { + local message="$1" + local code="${2:-1}" # Default to exit code 1 if not provided + error "$message" + exit "$code" +} + +# Only run a function if the script is executed (not sourced) +run_if_executed() { + local callback="$1" + shift # Remove the first argument + if [[ "${BASH_SOURCE[1]}" == "${0}" ]]; then + "$callback" "$@" + fi +} From 13141e40c8d69b2cc561659834d628fc323f16f4 Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Mon, 21 Oct 2024 13:21:39 -0400 Subject: [PATCH 04/12] Update fullSetup.sh - Improved readability of `fullSetup.sh` - Added logic to create self-cert SSL when running locally. This is needed to support testing with http/2 --- scripts/fullSetup.sh | 245 +++++++++++++++++++++++++++---------------- 1 file changed, 152 insertions(+), 93 deletions(-) diff --git a/scripts/fullSetup.sh b/scripts/fullSetup.sh index 6356486..ac498de 100644 --- a/scripts/fullSetup.sh +++ b/scripts/fullSetup.sh @@ -1,96 +1,155 @@ #!/bin/bash # Fully sets up server -HERE=`dirname $0` - -# ======================================================== -# General Ubuntu setup -# ======================================================== -header "Cleaning up apt library" -sudo rm -rvf /var/lib/apt/lists/* - -header "Upgrading cache limit" -sed -i 's/^.*APT::Cache-Limit.*$/APT::Cache-Limit \"100000000\";/' /etc/apt/apt.conf.d/70debconf - -header "Checking for package updates" -sudo apt-get update -header "Running upgrade" -sudo apt-get -y upgrade - -info "Updating max listeners, since npm uses a lot. Not sure exactly what they do, but the default max amount is not enough" -echo fs.inotify.max_user_watches=20000 | sudo tee -a /etc/sysctl.conf -echo vm.overcommit_memory=1 | sudo tee -a /etc/sysctl.conf - -# ======================================================== -# Installing required packages -# ======================================================== - -# -------------------------------------------------------- -# Docker -# -------------------------------------------------------- -header "Cleaning up old versions of Docker" -sudo apt-get remove docker docker-engine docker.io containerd runc - -header "Installing Docker prerequisites" -sudo apt-get install \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg \ - lsb-release - -header "Installing Docker from official GPG key" -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg - -header "Specify stable version of Docker" -echo \ - "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ - $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - -header "Installing Docker Engine" -sudo apt-get install docker-ce docker-ce-cli containerd.io - -header "Verifing that Docker Engine is running successfully. Container will automatically close" -sudo docker run hello-world - -header "Creating docker user group, so docker can be run without sudo" -sudo groupadd docker -sudo usermod -aG docker $USER - -header "Configuring Docker to run on boot" -sudo systemctl enable docker.service -sudo systemctl enable containerd.service - -header "Installing Docker Compose" -sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - -header "Making Docker Compose executable" -sudo chmod +x /usr/local/bin/docker-compose - -header "Create proxy network" -sudo docker network create nginx-proxy - - -# -------------------------------------------------------- -# Nginx -# -------------------------------------------------------- -info "Nginx will be inside a docker instance" -header "Purging any existing Nginx configurations" -sudo apt-get purge nginx nginx-common - - -# ======================================================== -# Setting up firewall -# ======================================================== -info "Since Nginx is inside docker, we must handle the firewall settings ourselves" -header "Setting up firewall" -# Enable firewall -sudo ufw enable -# Disable all connections -sudo ufw default allow outgoing -sudo ufw default deny incoming -# Only allow 80 and 443 (80 is required for certificates) -sudo ufw allow 80/tcp -sudo ufw allow ssh - -sudo sysctl -p + +set -e # Exit if any command fails +set -o pipefail # Exit if piped command (e.g. curl, apt-get) fails + +HERE=$(dirname $0) source "${HERE}/utils.sh" + +local_dev=false +while [[ "$#" -gt 0 ]]; do + case $1 in + --local) local_dev=true ;; + *) + echo "Unknown parameter passed: $1" + exit 1 + ;; + esac + shift +done + +check_root_privileges() { + if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root or with sudo privileges" + exit 1 + fi +} + +handle_apt_errors() { + # Function to handle errors during apt operations + if ! "$@"; then + error "ERROR: APT operation failed: $*" + warning "APT failures may affect the installation of necessary components. Check output above." + fi +} + +setup_ubuntu() { + header "Cleaning up apt library" + sudo rm -rvf /var/lib/apt/lists/* + + header "Upgrading cache limit" + sed -i 's/^.*APT::Cache-Limit.*$/APT::Cache-Limit \"100000000\";/' /etc/apt/apt.conf.d/70debconf + + header "Checking for package updates" + handle_apt_errors sudo apt-get update + + header "Running upgrade" + handle_apt_errors sudo apt-get -y upgrade + + info "Updating max listeners, since npm uses a lot. Not sure exactly what they do, but the default max amount is not enough" + echo fs.inotify.max_user_watches=20000 | sudo tee -a /etc/sysctl.conf + echo vm.overcommit_memory=1 | sudo tee -a /etc/sysctl.conf +} + +setup_docker() { + header "Installing Docker prerequisites" + if ! command -v docker > /dev/null 2>&1; then + sudo apt-get remove -y docker docker-engine docker.io containerd runc + sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release + + header "Adding Docker’s official GPG key" + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install -y docker-ce docker-ce-cli containerd.io + fi + + header "Verifying Docker Engine" + sudo docker run hello-world || true # Non-blocking + + if ! getent group docker > /dev/null; then + sudo groupadd docker + sudo usermod -aG docker $USER + fi + + header "Configuring Docker to start on boot" + sudo systemctl enable docker.service + sudo systemctl enable containerd.service + + if ! command -v docker-compose > /dev/null; then + header "Installing Docker Compose" + sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + fi + + if ! sudo docker network ls --filter name=^nginx-proxy$ --format "{{.Name}}" | grep -qw nginx-proxy; then + header "Creating proxy network" + sudo docker network create nginx-proxy + fi +} + +setup_self_cert() { + if ! command -v mkcert > /dev/null; then + header "Installing mkcert for local SSL development certificates" + sudo apt-get install -y libnss3-tools + curl -L "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64" -o mkcert + chmod +x mkcert + sudo mv mkcert /usr/local/bin/ + mkcert -install + fi + + header "Generating SSL certificates for localhost" + if [ ! -f "localhost+2.pem" ]; then + mkcert localhost 127.0.0.1 ::1 + info "Certificates generated at: $(pwd)" + else + info "Existing SSL certificates found. Skipping regeneration." + fi +} + +purge_nginx() { + header "Checking for Nginx on host machine" + info "Nginx will be inside a docker instance, rather than installed on the host machine. We will need to purge any existing Nginx configurations on the host machine." + + # Check if nginx is installed + if dpkg -l | grep -qw nginx; then + # Nginx is installed, ask for confirmation to purge + if prompt_confirm "Nginx configurations found. Do you want to purge them?"; then + header "Purging existing Nginx configurations" + sudo apt-get purge -y nginx nginx-common + else + info "Purging canceled by user." + fi + else + info "No existing Nginx configurations found. No action required." + fi +} + +setup_firewall() { + info "Since Nginx is inside docker, we must handle the firewall settings ourselves" + header "Setting up firewall" + # Enable firewall + sudo ufw enable + # Disable all connections + sudo ufw default allow outgoing + sudo ufw default deny incoming + # Only allow 80 and 443 (80 is required for certificates) + sudo ufw allow 80/tcp + sudo ufw allow ssh + sudo sysctl -p +} + +main() { + check_root_privileges + setup_ubuntu + setup_docker + if [ "$local_dev" = true ]; then + setup_self_cert + fi + purge_nginx + setup_firewall +} + +run_if_executed main "$@" From 5e9ed0e0563362026844b10642db80a9cf740ee7 Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Mon, 21 Oct 2024 13:22:03 -0400 Subject: [PATCH 05/12] Create docker-compose-local.yml - Created `docker-compose` file for self-cert testing --- docker-compose-local.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 docker-compose-local.yml diff --git a/docker-compose-local.yml b/docker-compose-local.yml new file mode 100644 index 0000000..639ba92 --- /dev/null +++ b/docker-compose-local.yml @@ -0,0 +1,19 @@ +version: '3.8' + +services: + nginx-local: + image: nginx:latest + container_name: nginx-local-dev + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/local.conf:/etc/nginx/conf.d/default.conf + - ./certs/localhost+2.pem:/etc/nginx/certs/localhost+2.pem + - ./certs/localhost+2-key.pem:/etc/nginx/certs/localhost+2-key.pem + networks: + - localnet + +networks: + localnet: + driver: bridge From a5f6090323f4a18d2b92baa9e14dfadd38f09606 Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Mon, 21 Oct 2024 20:31:38 -0400 Subject: [PATCH 06/12] =?UTF-8?q?Self-cert=20SSL=20working=F0=9F=A5=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Improved `fullSetup.sh` - Fixed `local.conf` - `docker-compose-local.yml` now starts up correctly. `curl -I --http2 -k https://localhost` shows that http/2 is working as expected --- .gitignore | 3 +- README.md | 8 ++- docker-compose-local.yml | 4 +- ...r-compose.yml => docker-compose-remote.yml | 2 +- nginx/conf.d/local.conf | 39 ++++++++++++ my_proxy.conf => nginx/conf.d/remote.conf | 0 scripts/fullSetup.sh | 59 ++++++++++++------- 7 files changed, 88 insertions(+), 27 deletions(-) rename docker-compose.yml => docker-compose-remote.yml (93%) create mode 100644 nginx/conf.d/local.conf rename my_proxy.conf => nginx/conf.d/remote.conf (100%) diff --git a/.gitignore b/.gitignore index 23b7ea2..8fde7f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # Ignore mkcert generated files (certificates and keys) -localhost+*.pem +certs/ +localhost+*.pem \ No newline at end of file diff --git a/README.md b/README.md index 75958c6..3e14e22 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Heavily inspired by [this article](https://olex.biz/2019/09/hosting-with-docker- 2. Run setup script: `chmod +x ./scripts/fullSetup.sh && ./scripts/fullSetup.sh` 3. Start docker: - a. `sudo docker-compose-local.yml up -d` (note which `.yml` file we're using) + a. `sudo docker-compose -f docker-compose.local.yml up -d` ### Running on a VPS 1. Set up VPS ([example](https://www.youtube.com/watch?v=Dwlqa6NJdMo&t=142s)). @@ -47,7 +47,7 @@ Heavily inspired by [this article](https://olex.biz/2019/09/hosting-with-docker- 5. Run setup script: `chmod +x ./scripts/fullSetup.sh && ./scripts/fullSetup.sh` 6. Start docker: - a. `sudo docker-compose up -d` (note which `.yml` file we're using) + a. `sudo docker-compose -f docker-compose.remote.yml up -d` ## Common commands @@ -56,6 +56,8 @@ Heavily inspired by [this article](https://olex.biz/2019/09/hosting-with-docker- ## Custom proxy -Custom proxy configurations can be put in the `my_proxy.conf` file. By default, this only contains one line: `client_max_body_size 100m;`. This raises the maximum payload size for uploading files. This is useful if you'd like users to have the ability to upload multiple images in one request, for example. +Custom proxy configurations can be put in the `nginx/conf.d/local.conf` or `nginx/conf.d/remote.conf` file, depending on if this will be running locally or remotely. + +By default, the local version contains the standard configuration for self-signed SSL setup. Both versions also contain `client_max_body_size 100m;`. This raises the maximum payload size for uploading files. This is useful if you'd like users to have the ability to upload multiple images in one request, for example. If you are not using custom configurations, you can remove the docker-compose line `- ./my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro`. diff --git a/docker-compose-local.yml b/docker-compose-local.yml index 639ba92..95c8608 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -8,9 +8,11 @@ services: - "80:80" - "443:443" volumes: - - ./nginx/local.conf:/etc/nginx/conf.d/default.conf + - ./nginx/conf.d/local.conf:/etc/nginx/conf.d/local.conf:ro - ./certs/localhost+2.pem:/etc/nginx/certs/localhost+2.pem - ./certs/localhost+2-key.pem:/etc/nginx/certs/localhost+2-key.pem + # Remove default config + - /dev/null:/etc/nginx/conf.d/default.conf:ro networks: - localnet diff --git a/docker-compose.yml b/docker-compose-remote.yml similarity index 93% rename from docker-compose.yml rename to docker-compose-remote.yml index c689aa3..0b2a570 100644 --- a/docker-compose.yml +++ b/docker-compose-remote.yml @@ -15,7 +15,7 @@ services: - dhparam:/etc/nginx/dhparam - certs:/etc/nginx/certs:ro - /var/run/docker.sock:/tmp/docker.sock:ro - - ./my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro + - ./nginx/conf.d/remote.conf:/etc/nginx/conf.d/remote.conf:ro - ./50x.html:/usr/share/nginx/html/errors/50x.html:ro networks: - proxy diff --git a/nginx/conf.d/local.conf b/nginx/conf.d/local.conf new file mode 100644 index 0000000..f7c0d02 --- /dev/null +++ b/nginx/conf.d/local.conf @@ -0,0 +1,39 @@ +client_max_body_size 100m; + +# Enable HTTP/2 globally +http2 on; + +server { + listen 80; + listen [::]:80; + server_name localhost; + + # Redirect HTTP to HTTPS + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name localhost; + + ssl_certificate /etc/nginx/certs/localhost+2.pem; + ssl_certificate_key /etc/nginx/certs/localhost+2-key.pem; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 10m; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # Root directory and index file + root /usr/share/nginx/html; + index index.html index.htm; + + location / { + try_files $uri $uri/ =404; + } + + # Additional configuration for reverse proxy or other settings can go here +} \ No newline at end of file diff --git a/my_proxy.conf b/nginx/conf.d/remote.conf similarity index 100% rename from my_proxy.conf rename to nginx/conf.d/remote.conf diff --git a/scripts/fullSetup.sh b/scripts/fullSetup.sh index ac498de..bbd8886 100644 --- a/scripts/fullSetup.sh +++ b/scripts/fullSetup.sh @@ -7,18 +7,6 @@ set -o pipefail # Exit if piped command (e.g. curl, apt-get) fails HERE=$(dirname $0) source "${HERE}/utils.sh" -local_dev=false -while [[ "$#" -gt 0 ]]; do - case $1 in - --local) local_dev=true ;; - *) - echo "Unknown parameter passed: $1" - exit 1 - ;; - esac - shift -done - check_root_privileges() { if [[ $EUID -ne 0 ]]; then echo "This script must be run as root or with sudo privileges" @@ -54,22 +42,22 @@ setup_ubuntu() { setup_docker() { header "Installing Docker prerequisites" - if ! command -v docker > /dev/null 2>&1; then + if ! command -v docker >/dev/null 2>&1; then sudo apt-get remove -y docker docker-engine docker.io containerd runc sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release header "Adding Docker’s official GPG key" curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io fi header "Verifying Docker Engine" - sudo docker run hello-world || true # Non-blocking + sudo docker run hello-world || true # Non-blocking - if ! getent group docker > /dev/null; then + if ! getent group docker >/dev/null; then sudo groupadd docker sudo usermod -aG docker $USER fi @@ -78,7 +66,7 @@ setup_docker() { sudo systemctl enable docker.service sudo systemctl enable containerd.service - if ! command -v docker-compose > /dev/null; then + if ! command -v docker-compose >/dev/null; then header "Installing Docker Compose" sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose @@ -91,7 +79,7 @@ setup_docker() { } setup_self_cert() { - if ! command -v mkcert > /dev/null; then + if ! command -v mkcert >/dev/null; then header "Installing mkcert for local SSL development certificates" sudo apt-get install -y libnss3-tools curl -L "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64" -o mkcert @@ -101,12 +89,19 @@ setup_self_cert() { fi header "Generating SSL certificates for localhost" - if [ ! -f "localhost+2.pem" ]; then + local CERT_DIR="${HERE}/../certs" + mkdir -p "${CERT_DIR}" + if [ ! -f "${CERT_DIR}/localhost+2.pem" ]; then + cd "${CERT_DIR}" mkcert localhost 127.0.0.1 ::1 - info "Certificates generated at: $(pwd)" + info "Certificates generated at: ${CERT_DIR}" + cd - else info "Existing SSL certificates found. Skipping regeneration." fi + + # Ensure the certificates are readable + chmod 644 "${CERT_DIR}/localhost+2.pem" } purge_nginx() { @@ -141,11 +136,33 @@ setup_firewall() { sudo sysctl -p } +SERVER_LOCATION="local" # Default to local main() { + while [[ $# -gt 0 ]]; do + key="$1" + case $key in + -l | --location) + if [ -z "$2" ] || [[ "$2" == -* ]]; then + echo "Error: Option $key requires an argument." + exit 1 + fi + SERVER_LOCATION="${2}" + shift # past argument + shift # past value + ;; + -h | --help) + echo "Usage: $0 [-l SERVER_LOCATION] [-h]" + echo " -l --location: Server location (e.g. \"local\", \"remote\")" + echo " -h --help: Show this help message" + exit 0 + ;; + esac + done + check_root_privileges setup_ubuntu setup_docker - if [ "$local_dev" = true ]; then + if [ "$SERVER_LOCATION" == "local" ]; then setup_self_cert fi purge_nginx From e58edddb9877cd30c3f89584205639fcc1d57db8 Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Tue, 22 Oct 2024 14:21:23 -0400 Subject: [PATCH 07/12] Updated syntax for Docker network - Updated syntax for Docker network --- docker-compose-local.yml | 7 ++++--- docker-compose-remote.yml | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker-compose-local.yml b/docker-compose-local.yml index 95c8608..c294537 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -14,8 +14,9 @@ services: # Remove default config - /dev/null:/etc/nginx/conf.d/default.conf:ro networks: - - localnet + - proxy networks: - localnet: - driver: bridge + proxy: + name: nginx-proxy + external: true \ No newline at end of file diff --git a/docker-compose-remote.yml b/docker-compose-remote.yml index 0b2a570..446d471 100644 --- a/docker-compose-remote.yml +++ b/docker-compose-remote.yml @@ -48,5 +48,5 @@ volumes: networks: proxy: - external: - name: nginx-proxy \ No newline at end of file + name: nginx-proxy + external: true \ No newline at end of file From c668c4df215aceb937555aa5c03aec7f687267ca Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Tue, 22 Oct 2024 14:23:03 -0400 Subject: [PATCH 08/12] Update fullSetup.sh - Improved logic for setting `/etc/sysctl.conf` settings --- scripts/fullSetup.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/fullSetup.sh b/scripts/fullSetup.sh index bbd8886..23f59a2 100644 --- a/scripts/fullSetup.sh +++ b/scripts/fullSetup.sh @@ -36,8 +36,21 @@ setup_ubuntu() { handle_apt_errors sudo apt-get -y upgrade info "Updating max listeners, since npm uses a lot. Not sure exactly what they do, but the default max amount is not enough" - echo fs.inotify.max_user_watches=20000 | sudo tee -a /etc/sysctl.conf - echo vm.overcommit_memory=1 | sudo tee -a /etc/sysctl.conf + # Remove any duplicates of fs.inotify.max_user_watches and vm.overcommit_memory + sudo sed -i '/^fs.inotify.max_user_watches=.*$/d' /etc/sysctl.conf + sudo sed -i '/^vm.overcommit_memory=.*$/d' /etc/sysctl.conf + # Add fs.inotify.max_user_watches if not present + if ! grep -q "^fs.inotify.max_user_watches=20000$" /etc/sysctl.conf; then + echo "fs.inotify.max_user_watches=20000" | sudo tee -a /etc/sysctl.conf + else + info "fs.inotify.max_user_watches is already set" + fi + # Add vm.overcommit_memory if not present + if ! grep -q "^vm.overcommit_memory=1$" /etc/sysctl.conf; then + echo "vm.overcommit_memory=1" | sudo tee -a /etc/sysctl.conf + else + info "vm.overcommit_memory is already set" + fi } setup_docker() { From 2c28bdde5763dec1857eaf96d0d34e03d27dc759 Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Tue, 22 Oct 2024 14:23:28 -0400 Subject: [PATCH 09/12] Update fullSetup.sh - Docker is no longer reinstalled when running `fullSetup.sh` multiple times --- scripts/fullSetup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/fullSetup.sh b/scripts/fullSetup.sh index 23f59a2..08d56d1 100644 --- a/scripts/fullSetup.sh +++ b/scripts/fullSetup.sh @@ -56,7 +56,6 @@ setup_ubuntu() { setup_docker() { header "Installing Docker prerequisites" if ! command -v docker >/dev/null 2>&1; then - sudo apt-get remove -y docker docker-engine docker.io containerd runc sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release header "Adding Docker’s official GPG key" @@ -65,6 +64,8 @@ setup_docker() { echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io + else + info "Detected Docker version: $(docker --version)" fi header "Verifying Docker Engine" From 27e792f117ab1ffc7d0fb5d4df2a748f6472c15f Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Tue, 22 Oct 2024 14:24:37 -0400 Subject: [PATCH 10/12] Update local.conf - Local Nginx config updated to proxy_pass to the correct container. Now the local development UI uses http/2 when the URL is `https://localhost` (note that we don't include the port)! --- nginx/conf.d/local.conf | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/nginx/conf.d/local.conf b/nginx/conf.d/local.conf index f7c0d02..2f051c6 100644 --- a/nginx/conf.d/local.conf +++ b/nginx/conf.d/local.conf @@ -27,13 +27,12 @@ server { ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; - # Root directory and index file - root /usr/share/nginx/html; - index index.html index.htm; - + # Proxying requests to React app location / { - try_files $uri $uri/ =404; + proxy_pass http://ui:3000; # Change to UI container's name + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; } - - # Additional configuration for reverse proxy or other settings can go here } \ No newline at end of file From 13a6ffa81e225a810c7e556460cdd45bfe730de8 Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Tue, 22 Oct 2024 20:23:43 -0400 Subject: [PATCH 11/12] Remove "hello-world" container - Docker testing container is now removed after we verify that the Docker engine is working correctly --- scripts/fullSetup.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/fullSetup.sh b/scripts/fullSetup.sh index 08d56d1..a79149a 100644 --- a/scripts/fullSetup.sh +++ b/scripts/fullSetup.sh @@ -40,8 +40,8 @@ setup_ubuntu() { sudo sed -i '/^fs.inotify.max_user_watches=.*$/d' /etc/sysctl.conf sudo sed -i '/^vm.overcommit_memory=.*$/d' /etc/sysctl.conf # Add fs.inotify.max_user_watches if not present - if ! grep -q "^fs.inotify.max_user_watches=20000$" /etc/sysctl.conf; then - echo "fs.inotify.max_user_watches=20000" | sudo tee -a /etc/sysctl.conf + if ! grep -q "^fs.inotify.max_user_watches=30000$" /etc/sysctl.conf; then + echo "fs.inotify.max_user_watches=30000" | sudo tee -a /etc/sysctl.conf else info "fs.inotify.max_user_watches is already set" fi @@ -70,6 +70,10 @@ setup_docker() { header "Verifying Docker Engine" sudo docker run hello-world || true # Non-blocking + # Remove the "hello-world" container to avoid clutter + if sudo docker ps -a --filter "ancestor=hello-world" --format "{{.ID}}" | grep -q .; then + sudo docker rm $(sudo docker ps -a --filter "ancestor=hello-world" --format "{{.ID}}") + fi if ! getent group docker >/dev/null; then sudo groupadd docker From ff5fe54a621ba45444b5f971921508d68ab83b9d Mon Sep 17 00:00:00 2001 From: MattHalloran Date: Tue, 22 Oct 2024 20:24:32 -0400 Subject: [PATCH 12/12] Update local.conf - local Nginx config updated to use variables for DNS resolution, which should help ensure that Nginx works even when started before the app's containers --- nginx/conf.d/local.conf | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/nginx/conf.d/local.conf b/nginx/conf.d/local.conf index 2f051c6..00aa927 100644 --- a/nginx/conf.d/local.conf +++ b/nginx/conf.d/local.conf @@ -1,5 +1,7 @@ client_max_body_size 100m; +resolver 127.0.0.11 valid=30s; + # Enable HTTP/2 globally http2 on; @@ -27,12 +29,40 @@ server { ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; - # Proxying requests to React app + # Use variables to defer DNS resolution + set $ui_upstream ui:3000; # Change to match your UI server's name and port + set $api_upstream server:5329; # Change to match your API server's name and port + + # UI Server Proxy location / { - proxy_pass http://ui:3000; # Change to UI container's name + # proxy_pass http://$ui_upstream; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_http_version 1.1; + # proxy_set_header Connection ""; + # proxy_buffering off; # For WebSocket support + # proxy_request_buffering off; + proxy_pass http://$ui_upstream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } + + # API Server Proxy + location /api/ { + proxy_pass http://$api_upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + # Support for WebSocket connections + location /sockjs-node { + proxy_pass http://$ui_upstream/sockjs-node; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } } \ No newline at end of file