diff --git a/Dockerfile b/Dockerfile index f9bc6eb..3d4be9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ENV DIFFIE_HELLMAN='' \ # Here we install open resty and generate dhparam.pem file. # You can specify DIFFIE_HELLMAN=true to force regeneration of that file on first run # also we create fallback ssl keys -RUN apk --no-cache add bash openssl \ +RUN apk --no-cache add bash openssl python3 py3-pip \ && /usr/local/openresty/luajit/bin/luarocks install lua-resty-auto-ssl $AUTO_SSL_VERSION \ && openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ -subj '/CN=sni-support-required-for-valid-ssl' \ @@ -28,9 +28,10 @@ RUN apk --no-cache add bash openssl \ && rm /etc/nginx/conf.d/default.conf COPY nginx.conf snippets /usr/local/openresty/nginx/conf/ -COPY entrypoint.sh /entrypoint.sh +COPY entrypoint.py /entrypoint.py + +RUN chmod +x /entrypoint.py VOLUME /etc/resty-auto-ssl -ENTRYPOINT ["/entrypoint.sh"] -CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"] +CMD ["python3", "/entrypoint.py"] \ No newline at end of file diff --git a/README.md b/README.md index 48c7140..32bf588 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +# About this fork +This repo was forked from [valian/docker-nginx-auto-ssl](https://github.com/valian/docker-nginx-auto-ssl). I have made the following enhancements: + +- **Entry Point Change:** The original `entrypoint.sh` has been replaced with `entrypoint.py`, a Python script that performs the same functionality. This change was made because I am more comfortable using Python for modifications and enhancements. +- **Multiline Environment Variable Support:** The `SITES` environment variable can now be specified as a multiline string, which is particularly useful for managing multiple sites to proxy. Please see the format in the example below. +- **Multi-Architecture Support:** The Docker image has been rebuilt to support both x86 and ARM architectures. The updated image can be found on [Docker Hub](https://hub.docker.com/r/pswerlang/nginx-auto-ssl). + + # docker-nginx-auto-ssl *The simpliest solution to add SSL cert to your site* @@ -13,7 +21,9 @@ This is possible thanks to [OpenResty](https://github.com/openresty/openresty) a # Usage -Quick start to generate and auto-renew certs for your blog / application: +## Quick start + +To generate and automatically renew certificates for your application, use the following commands: ```Bash # replace these values @@ -28,7 +38,7 @@ docker run -d \ -e ALLOWED_DOMAINS="$DOMAIN" \ -e SITES="$DOMAIN=$APP_ADDRESS" \ -v ssl-data:/etc/resty-auto-ssl \ - valian/docker-nginx-auto-ssl + pswerlang/nginx-auto-ssl # display logs from container, to check if everything is fine. docker logs nginx-auto-ssl @@ -37,11 +47,10 @@ docker logs nginx-auto-ssl [Docker-compose](https://docs.docker.com/compose/) example: ```yaml -# docker-compose.yml -version: '2' +# compose.yaml services: nginx: - image: valian/docker-nginx-auto-ssl + image: pswerlang/nginx-auto-ssl restart: on-failure ports: - 80:80 @@ -83,23 +92,30 @@ Available configuration options: | REDIS_DB | `db_number` | The Redis database number used by lua-resty-auto-ssl to save certificates. `0` by default | | REDIS_KEY_PREFIX | `some-prefix` | Prefix all keys stored in Redis with this string. `''` by default | +## Proxying Multiple Sites -If you want to proxy multiple sites (probably the most common case, that's why I've made it possible to achieve without custom configuration): +If you want to proxy multiple sites, you can run: -```Bash -docker run -d \ - --name nginx-auto-ssl \ - --restart on-failure \ - -p 80:80 \ - -p 443:443 \ - -e ALLOWED_DOMAINS=example.com \ - -e SITES='example.com=localhost:5432;*.example.com=localhost:8080' \ - valian/docker-nginx-auto-ssl +```yaml +# compose.yaml +services: + nginx: + image: pswerlang/nginx-auto-ssl + restart: on-failure + ports: + - 80:80 + - 443:443 + environment: + ALLOWED_DOMAINS: 'example.com' + SITES: | + example.com=myapp:80 + api.example.com=api:80 + app.example.com=app:80 ``` -# Customization +## Customization -## Includes from `/etc/nginx/conf.d/*.conf` +### Includes from `/etc/nginx/conf.d/*.conf` Additional server blocks are automatically loaded from `/etc/nginx/conf.d/*.conf`. If you want to provide your own configuration, you can either use volumes or create custom image. @@ -122,23 +138,26 @@ server { } ``` -Volumes way +Volumes method -```Bash -# instead of $PWD, use directory with your custom configurations -docker run -d \ - --name nginx-auto-ssl \ - --restart on-failure \ - -p 80:80 \ - -p 443:443 \ - -v $PWD:/etc/nginx/conf.d - valian/docker-nginx-auto-ssl +```yaml +# compose.yaml +services: + nginx: + image: pswerlang/nginx-auto-ssl + restart: on-failure + ports: + - 80:80 + - 443:443 + volumes: + # instead of . use directory with your configurations + - .:/etc/nginx/conf.d ``` Custom image way ```Dockerfile -FROM valian/docker-nginx-auto-ssl +FROM pswerlang/nginx-auto-ssl # instead of . use directory with your configurations COPY . /etc/nginx/conf.d @@ -170,13 +189,13 @@ server { ``` -## Your own `nginx.conf` +## Custom Nginx Configuration -If you have custom requirements and other customization options are not enough, you can easily provide your own configuration. +If additional customization is required, you can provide your own Nginx configuration. Example `Dockerfile`: ```Dockerfile -FROM valian/docker-nginx-auto-ssl +FROM pswerlang/nginx-auto-ssl COPY nginx.conf /usr/local/openresty/nginx/conf/ ``` @@ -251,6 +270,7 @@ There's more to it, eg locks across all workers to only generate one certificate # CHANGELOG +* **01-11-2024** - Added multi-architecture support, Python entrypoint, and multiline SITES support * **11-11-2019** - Added gzip support and dropped TLS 1.0 and 1.1 #33 * **18-04-2019** - Added WebSocket support #22 * **29-05-2017** - Fixed duplicate redirect location after container restart #2 diff --git a/build-docker.sh b/build-docker.sh new file mode 100755 index 0000000..3779a25 --- /dev/null +++ b/build-docker.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +TAG=$1 + +docker buildx build --platform linux/amd64,linux/arm64 -t pswerlang/nginx-auto-ssl:$TAG --push . +docker buildx build --platform linux/amd64,linux/arm64 -t pswerlang/nginx-auto-ssl:latest --push . \ No newline at end of file diff --git a/entrypoint.py b/entrypoint.py new file mode 100755 index 0000000..55caf1e --- /dev/null +++ b/entrypoint.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +import os +import shutil +import subprocess + +RESTY_CONF_DIR = "/usr/local/openresty/nginx/conf" +NGINX_CONF_DIR = "/etc/nginx/conf.d" + +# openresty will change it later on his own, right now we're just giving it access +os.chmod("/etc/resty-auto-ssl", 0o777) + +# Check if dhparam.pem file exists in the volume, generate if it doesn't +dhparam_path = "/etc/resty-auto-ssl/dhparam.pem" +if not os.path.isfile(dhparam_path): + if os.environ.get("DIFFIE_HELLMAN"): + subprocess.call(["openssl", "dhparam", "-out", dhparam_path, "2048"]) + else: + shutil.copyfile(os.path.join(RESTY_CONF_DIR, "dhparam.pem"), dhparam_path) + +# Prepare configuration files for each site specified +sites = os.environ.get("SITES") +if sites: + # Check if the sites variable contains semicolons or new lines + if ";" in sites: + sites_separated = sites.split(";") + else: + # Handle multi-line input + sites_separated = [line.strip() for line in sites.strip().splitlines() if line.strip()] + + for name_eq_endpoint in sites_separated: + server_name, server_endpoint = name_eq_endpoint.split("=")[0], name_eq_endpoint.split("=")[1] + raw_server_endpoint = server_endpoint.split("//")[-1] + os.environ["SERVER_NAME"] = server_name + os.environ["SERVER_ENDPOINT"] = raw_server_endpoint + + with open(os.path.join(RESTY_CONF_DIR, "server-proxy.conf"), "r") as template_file: + template = template_file.read() + + config = template.replace("$SERVER_NAME", server_name).replace("$SERVER_ENDPOINT", raw_server_endpoint) + + with open(os.path.join(NGINX_CONF_DIR, f"{server_name}.conf"), "w") as conf_file: + conf_file.write(config) + +# If no sites are specified, check if the Nginx configuration directory is empty and copy the default server configuration +elif not os.listdir(NGINX_CONF_DIR): + shutil.copyfile(os.path.join(RESTY_CONF_DIR, "server-default.conf"), os.path.join(NGINX_CONF_DIR, "default.conf")) + +# Add "include force-https.conf;" directive to the OpenResty HTTP server configuration file if FORCE_HTTPS is set to "true" +if os.environ.get("FORCE_HTTPS") == "true": + resty_http_conf = os.path.join(RESTY_CONF_DIR, "resty-server-http.conf") + if "force-https.conf" not in open(resty_http_conf).read(): + with open(resty_http_conf, "a") as conf_file: + conf_file.write("include force-https.conf;\n") + +# Substitute environment variables in the OpenResty HTTP server configuration file +allowed_domains = os.environ.get("ALLOWED_DOMAINS", "") +letsencrypt_url = os.environ.get("LETSENCRYPT_URL", "") +resolver_address = os.environ.get("RESOLVER_ADDRESS", "") +storage_adapter = os.environ.get("STORAGE_ADAPTER", "") +redis_host = os.environ.get("REDIS_HOST", "") +redis_port = os.environ.get("REDIS_PORT", "") +redis_db = os.environ.get("REDIS_DB", "") +redis_key_prefix = os.environ.get("REDIS_KEY_PREFIX", "") + +with open(os.path.join(RESTY_CONF_DIR, "resty-http.conf"), "r") as template_file: + template = template_file.read() + +config = template.replace("$ALLOWED_DOMAINS", allowed_domains).replace("$LETSENCRYPT_URL", letsencrypt_url).replace( + "$RESOLVER_ADDRESS", resolver_address).replace("$STORAGE_ADAPTER", storage_adapter).replace("$REDIS_HOST", redis_host).replace( + "$REDIS_PORT", redis_port).replace("$REDIS_DB", redis_db).replace("$REDIS_KEY_PREFIX", redis_key_prefix) + +with open(os.path.join(RESTY_CONF_DIR, "resty-http.conf"), "w") as conf_file: + conf_file.write(config) + +# Execute the command specified in the '@' environment variable if provided. +command = os.environ.get("@") +if command: + command_list = command.split() + try: + subprocess.run(command_list) # Wait for the command to complete + except Exception as e: + print(f"Failed to start command '{command}': {e}") + sys.exit(1) + +# Now, execute OpenResty. +command_list = ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"] +try: + subprocess.run(command_list) # Run OpenResty and wait for it to complete +except Exception as e: + print(f"Failed to start OpenResty: {e}") + sys.exit(1) \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index 9f45abd..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash - -RESTY_CONF_DIR="/usr/local/openresty/nginx/conf" -NGINX_CONF_DIR="/etc/nginx/conf.d" - -# openresty will change it later on his own, right now we're just giving it access -chmod 777 /etc/resty-auto-ssl - -# we want to keep dhparam.pem in volume, to generate just one time -if [ ! -f "/etc/resty-auto-ssl/dhparam.pem" ]; then - if [ -n "$DIFFIE_HELLMAN" ]; then - openssl dhparam -out /etc/resty-auto-ssl/dhparam.pem 2048 - else - cp ${RESTY_CONF_DIR}/dhparam.pem /etc/resty-auto-ssl/dhparam.pem - fi -fi - - -# if $SITES is defined, we should prepare configuration files -# example usage: -# -# -e SITES="db.example.com=localhost:5432;app.example.com=http://localhost:8080" -# -# it will create 2 files: -# -# 1. /etc/nginx/conf.d/db.example.com.conf using $SERVER_ENDPOINT=localhost:5432 and $SERVER_NAME=db.example.com -# 2. /etc/nginx/conf.d/app.example.com.conf using $SERVER_ENDPOINT=localhost:8080 and $SERVER_NAME=app.example.com - -if [ -n "$SITES" ]; then - # lets read all backends, separated by ';' - IFS=\; read -a SITES_SEPARATED <<<"$SITES" - - # for each backend (in form of server_name=endpoint:port) we create proper file - for NAME_EQ_ENDPOINT in "${SITES_SEPARATED[@]}"; do - RAW_SERVER_ENDPOINT=${NAME_EQ_ENDPOINT#*=} - export SERVER_NAME=${NAME_EQ_ENDPOINT%=*} - export SERVER_ENDPOINT=${RAW_SERVER_ENDPOINT#*//} # it clears url scheme, like http:// or https:// - envsubst '$SERVER_NAME $SERVER_ENDPOINT' \ - < ${RESTY_CONF_DIR}/server-proxy.conf \ - > ${NGINX_CONF_DIR}/${SERVER_NAME}.conf - done - unset SERVER_NAME SERVER_ENDPOINT - - -# if $SITES isn't defined, let's check if $NGINX_CONF_DIR is empty -elif [ ! "$(ls -A ${NGINX_CONF_DIR})" ]; then - # if yes, just copy default server (similar to default from docker-openresty, but using https) - cp ${RESTY_CONF_DIR}/server-default.conf ${NGINX_CONF_DIR}/default.conf -fi - - -if [ "$FORCE_HTTPS" == "true" ]; then - # only do this, if it's first run - if ! grep -q "force-https.conf" ${RESTY_CONF_DIR}/resty-server-http.conf - then - echo "include force-https.conf;" >> ${RESTY_CONF_DIR}/resty-server-http.conf - fi -fi - - -# let's substitute $ALLOWED_DOMAINS, $LETSENCRYPT_URL and $RESOLVER_ADDRESS into OpenResty configuration -envsubst '$ALLOWED_DOMAINS,$LETSENCRYPT_URL,$RESOLVER_ADDRESS,$STORAGE_ADAPTER,$REDIS_HOST,$REDIS_PORT,$REDIS_DB,$REDIS_KEY_PREFIX' \ - < ${RESTY_CONF_DIR}/resty-http.conf \ - > ${RESTY_CONF_DIR}/resty-http.conf.copy \ - && mv ${RESTY_CONF_DIR}/resty-http.conf.copy ${RESTY_CONF_DIR}/resty-http.conf - -exec "$@" \ No newline at end of file