commit 961e7aebe4faee4bb060553f6cc4f7b98092a4a5 Author: Minecon724 Date: Fri Jul 11 14:48:23 2025 +0200 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..737e26b --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +run/ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..737e26b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +run/ \ No newline at end of file diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..dee2e7d --- /dev/null +++ b/Containerfile @@ -0,0 +1,30 @@ +# Remember to update! +FROM docker.io/nginx:1.29.0-alpine-slim + +ENV DOMAIN="example.localhost" +ENV SERVER_ID="server" +ENV RELOAD_FILE="/var/run/nginx-reload" +ENV ACME_CHALLENGE_URL="http://acme-challenge.${DOMAIN}/.well-known/acme-challenge/" + +RUN apk add --no-cache \ + inotify-tools # For reloading + +# Copy the configuration files +COPY nginx /etc/nginx/ + +# Copy the dummy certificate files +COPY certificates /etc/ssl/ + +# Copy the entrypoint scripts +COPY --chmod=0755 docker-entrypoint.d /docker-entrypoint.d/ + +# Copy the scripts +COPY --chmod=0755 scripts/ /opt/scripts/ +RUN ln -s /opt/scripts/reload.sh /usr/local/bin/reload + +# Create the volumes for certificates and website files +VOLUME /etc/ssl/certs +VOLUME /var/www/html + +# Expose the ports for HTTP and HTTPS +EXPOSE 80/tcp 443/tcp 443/udp \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..437c0ea --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +This is a container that helps host a static website. + +## Configuration + +**Requires** the following mounts: +- `/etc/ssl/certs/$DOMAIN`: For certificates (`fullchain.pem` and `privkey.pem`) +- `/var/www/html/$DOMAIN`: Website files, `index.html` goes right here + +**Requires** the following environment variables: +- `DOMAIN`: The domain +- `ACME_CHALLENGE_HOST`: The source of `.well-known/acme-challenge` + +You're also encouraged to provide your own: +- `/etc/ssl/dhparam.pem`, generated with: + ```bash + openssl dhparam -out dhparam.pem 4096 + ``` +- `/etc/ssl/snakeoil.key` & `/etc/ssl/snakeoil.pem`, generated with: + ```bash + openssl req -new -x509 -days 398 -noenc -out snakeoil.pem -keyout snakeoil.key -subj "/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd" + ``` +- `SERVER_ID`: How to call this server (for info) +- **Mount** `/var/run/nginx-reload`: modify this file to reload nginx + +## TODO +- support for multiple domains \ No newline at end of file diff --git a/certificates/dhparam.pem b/certificates/dhparam.pem new file mode 100644 index 0000000..e2d3b59 --- /dev/null +++ b/certificates/dhparam.pem @@ -0,0 +1,13 @@ +-----BEGIN DH PARAMETERS----- +MIICDAKCAgEAgzMa8BTPT60M95CnRmSc69n9b+hY+9T8jte8HNEQfu07sJQkKJfK +c5znc+bR1fnt0CFexz2rm4TUig082R8zEopfoXztR2XEtIEqiJ9Lt2HGx9t2GFi+ +aYAvB7Y8qvFq67dJdIzqVEkjoRBZwUiOAbRuaL54LOz5pBBv0217ezzbfEYtItAu +DdOQOZEfQ1+N7PJ9QQpfDJVl7rKfzna0gF3Wox1q5QtNqks44TWYiZqs0eOWkged +18PEsrwYpeiEqz7/ytaLONB1YU2FSrnYQFE9e7iMRMddEPRoor3VKeRC7gWTNIsB +Kkt2LfDX0lXO7bEuKMEoGvt7xkJ2XlUDjtc3htF5BKcuCWpygXTBVtZ+tkdPpjYP +fp/gS2X060GPI3Egxtua2kHj3zHiebQHn000CxLgSbUUwNxzhThoMONF7v4Z3R9D +TUYLsu1No6FzQbotJiFepoSkI9WqhdMO/9VYu/EwBGq+Wlhqo9mh9KQL4uMdIC4P +v1ZoazRRn6ioaPLwabwvsZnzu+TxQBuAolDB2rN7dSljXNinG/y2Jv6y5LJZlFI9 +nSVm22Sm+mwHz+rlfBmipaIxMTxN4UyxSx9oLEMj0c0PDXmbPaxJ78tw2+RG9dLn +cbXROxsE/0MehRpDM6ksn/O2XRXFeSs0ol6Z4EZ98zxUa4crDanUD0cCAQICAgFF +-----END DH PARAMETERS----- diff --git a/certificates/snakeoil.key b/certificates/snakeoil.key new file mode 100644 index 0000000..876bba8 --- /dev/null +++ b/certificates/snakeoil.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc2YmG90vmG/Og +1hpufu81YjDaPNGOdsDcjagA0mFFs2kGATrg95JZNcu5Aef8ropEIV9mu3++zP8K +VjPD+7HJ2eCkT1tT4IAy9q4JQAEv1efyjHbcfgXAhPRhil2hSICS2lCnL0dTPVFk +/Pjj5tl+vatzoiLDmnSpbETjID+dG7IBYxOtVlH61ZEMV2jUHnGPcZfv5UFMQD6m +hqV1VSzTO+X4EhT1fVIRzvM3A21YbKof3z9HxxldizhjV7kAihBrD/2i1EDOAZwF +tufQVG8Dy5+pUl21xcBfcmAJ8Bqz5Dk6jwClRUMSGv+WNb+RG7an1UkszVnPGv0G +D65UnVDtAgMBAAECggEABIUSNrgobj7rs0K2e0TvRdcq20iL3F/LTig7BDtKANht +YZFUYGbIsLk0YGas1gPHciIBtxNGx8Ofw5kkiZIE3zXtjkdOfe62CPT59lgAPjeh +PdpIk3YjzX8bFkTz6hlXAkpkBKvnyIkUeZ3gxR9EG78re1wZ3Il0ckQ0O7a8/ozZ +D4OSnm57Ya9vSDboph+kfa9zH30dPKbkjyaJOsFGgn0/wgQ/9omir3Ty0nqMPD3r +vgertcek5GXXXd0HxZ2nDPX5K3v3eKNfq5ivdDzEi2rH4JHrEjIUaUehqNhl+ah8 +v5u9lfaRsN0AvV7Cx3ZoV2sh9s/F0vO9N42bhAQteQKBgQD4ldOFXHi1yQAnyNbT +MsvU3UAdffbzKg7u3NxtBInFLpXbgkTDkCzQK1flyJeYMBSQQxniPhNKVVMjJaOa +FBlPFssf0uV3VHHj9bJsegnHC1VUaE6mUMIlZ9L1En1An21q+fdGdf58OK0ozWzP +SeK42pAGZul/hXGKkTl1NXk8OQKBgQDjb+zdSUJsZfmunYHZ6BQhDBpTFy0a4f0q +LcGf4e4LWzYArN2X3buKhPRNn8j7g6ThU9c2HErAYAVbfRdMRjW+372EWf+v7Skq +KKq3kf2WbFn9UDo6yNs3dQOWM8yX7w+1LsvkbDqUYMOpYdwj0Ts5Sj7Xa82vrnmm +gdloMcLiVQKBgGcE8JwcLopnuoDCYwUzz/Vvm9qdEXLEw1uKfG9Rqibln4VQ/15s +qaW70LmR4AFaK6t9o4R3ZVcw9TtjpeF84uA7+gI+TIqfnHN0p3T9PoAW2k4YzD9w +yITn+i1GdFILwDTNUwUIcWbZtilquOVPIX6qVXXRwILwspVSihVhC9VBAoGBAL4Z +fUfwxd5I1gtHh5OVUc3VieNNidvgbHTmzeJPO2KumFK0KnuXT/wV6QVAuwLPI+9a +3pVRzIFDQPcZdXHBdYbXCFcpyndHqZKdbyQEmIs0gXsjpagg3mpaGedf4HBV1zE/ +rhh9BfGjd6eKLjCZ4ACguCni54ciNHgiLI/Ul+oJAoGAQMprKI4uPfAnvVJuEN20 +P8prsJpcudsgdDNazodKEWo6uhuqcpHF6aECgulSfrn6ewmt4aiLrzmiWXlC2z1z +EXDKSdk+jA/7RJL+oLN4WYBI9qtA5tAdYbfcoJmZgB/jXdqkPe3aD/mdqn7NfxmG +zIjGgAIFvv2yb0Oph3pGMXo= +-----END PRIVATE KEY----- diff --git a/certificates/snakeoil.pem b/certificates/snakeoil.pem new file mode 100644 index 0000000..1c88148 --- /dev/null +++ b/certificates/snakeoil.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIUcywFzfoAiMaUnAfJah7Yy3pq4rAwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yNTA3MTAxOTI5MjRaGA85OTk5 +MTIzMTE5MjkyNFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANzZiYb3S+Yb86DWGm5+7zViMNo80Y52wNyNqADS +YUWzaQYBOuD3klk1y7kB5/yuikQhX2a7f77M/wpWM8P7scnZ4KRPW1PggDL2rglA +AS/V5/KMdtx+BcCE9GGKXaFIgJLaUKcvR1M9UWT8+OPm2X69q3OiIsOadKlsROMg +P50bsgFjE61WUfrVkQxXaNQecY9xl+/lQUxAPqaGpXVVLNM75fgSFPV9UhHO8zcD +bVhsqh/fP0fHGV2LOGNXuQCKEGsP/aLUQM4BnAW259BUbwPLn6lSXbXFwF9yYAnw +GrPkOTqPAKVFQxIa/5Y1v5EbtqfVSSzNWc8a/QYPrlSdUO0CAwEAAaNTMFEwHQYD +VR0OBBYEFMFWyHV3/5i/Ue5HsCST/wLRPeOEMB8GA1UdIwQYMBaAFMFWyHV3/5i/ +Ue5HsCST/wLRPeOEMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AJ2sUw5r3JL8LnczTaiiyn36vuCh0eOlzKDadTH9Wk5QGYShKx8a8EcePJ4+l7yL +0f5S9npxXCSRF4daG0BZNBQSTpGu/sT6hJ5/Knmpso8w2PWFPMaYhNi+6zQJ3Bno +sfsJgoVsklUWMRuCVcoyLpAbc0UEb+uuDuLeQTdYP9PzmDp8/UUZBvJ/NznGrFZt +aKnx/g6Qu0gdQKH5pGL3C707CxsvM54UXi5pswbs0kqql9oVTKsAukPeGMXl2num +dSZy+yNAGCsuUGJhG0yatZh473nYYtV4fzzvPIorCrjjvada6Z8EM3RQBOmDT9Oc +RRed5U4jJ3IRbYBF1Q8tagg= +-----END CERTIFICATE----- diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fa61ed3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +services: + webserver: + build: + context: . + dockerfile: Containerfile + environment: + DOMAIN: example.localhost + SERVER_ID: development + ACME_CHALLENGE_URL: http://localhost/acme-challenge + ports: + - "80:80" + - "443:443" + - "443:443/udp" + volumes: + - ./run/html:/var/www/html + - ./run/certs:/etc/ssl/certs + - ./run/reload:/var/run/nginx-reload \ No newline at end of file diff --git a/docker-entrypoint.d/10-initialize-certificates.sh b/docker-entrypoint.d/10-initialize-certificates.sh new file mode 100755 index 0000000..6d9f1c9 --- /dev/null +++ b/docker-entrypoint.d/10-initialize-certificates.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -euo pipefail + +ls -la /etc/ssl +ls -la /etc/nginx + +CERTIFICATE_ROOT="/etc/ssl" +CERTIFICATE_DOMAIN_ROOT="$CERTIFICATE_ROOT/certs/$DOMAIN" + +mkdir -p "$CERTIFICATE_DOMAIN_ROOT" + +cp -n "$CERTIFICATE_ROOT/snakeoil.pem" "$CERTIFICATE_DOMAIN_ROOT/fullchain.pem" +cp -n "$CERTIFICATE_ROOT/snakeoil.key" "$CERTIFICATE_DOMAIN_ROOT/privkey.pem" + +chmod 700 "$CERTIFICATE_DOMAIN_ROOT/fullchain.pem" +chmod 700 "$CERTIFICATE_DOMAIN_ROOT/privkey.pem" \ No newline at end of file diff --git a/docker-entrypoint.d/50-watch-reload.sh b/docker-entrypoint.d/50-watch-reload.sh new file mode 100644 index 0000000..be61727 --- /dev/null +++ b/docker-entrypoint.d/50-watch-reload.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -euo pipefail + +echo "Starting reload watcher..." + +/opt/scripts/watch-reload.sh & \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..cad2c74 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,90 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_user [$time_local] "$request" ' + '$status $body_bytes_sent bytes "$http_referer" ' + '"$http_x_forwarded_for"'; + + # While I removed PII from the above log format, still better not logging + access_log /dev/null main; # /var/log/nginx/access.log main; + + + server_tokens off; + + sendfile on; + tcp_nopush on; + + quic_retry on; + quic_gso on; + ssl_early_data on; # READ https://blog.cloudflare.com/introducing-0-rtt/#whats-the-catch + + keepalive_timeout 65; + + gzip on; + gzip_types *; + gzip_min_length 1000; + gzip_proxied any; + + http2 on; + + add_header Alt-Svc 'h3=":443"; ma=86400'; + add_header Strict-Transport-Security "max-age=63072000; preload" always; + + # intermediate configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ecdh_curve X25519:prime256v1:secp384r1; + 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:DHE-RSA-CHACHA20-POLY1305; + ssl_prefer_server_ciphers off; + + ssl_session_timeout 1h; + ssl_session_cache shared:MozSSL:10m; # about 40000 sessions + + # Make sure to generate it first + ssl_dhparam /etc/ssl/dhparam.pem; + + # OCSP stapling + ssl_stapling on; + ssl_stapling_verify on; + + # async 'resolver' is important for proper operation of OCSP stapling + resolver [2001:4860:4860::8888] 8.8.8.8; + + # If certificates are marked OCSP Must-Staple, consider managing the + # OCSP stapling cache with an external script, e.g. certbot-ocsp-fetcher + + # HTTPS redirect / HSTS + server { + listen 80 default_server; + listen [::]:80 default_server; + + return 301 https://$host$request_uri; + } + + # default HTTPS server + server { + listen 443 ssl default_server; + listen 443 quic reuseport default_server; + listen [::]:443 ssl default_server; + + ssl_certificate /etc/ssl/snakeoil.pem; + ssl_certificate_key /etc/ssl/snakeoil.key; + + server_name _; + + return 444; + } + + include /etc/nginx/conf.d/*.conf; +} diff --git a/nginx/templates/default.conf.template b/nginx/templates/default.conf.template new file mode 100644 index 0000000..956ae02 --- /dev/null +++ b/nginx/templates/default.conf.template @@ -0,0 +1,18 @@ +server { + listen 443 ssl; + listen 443 quic; + listen [::]:443 ssl; + listen [::]:443 quic; + + server_name ${DOMAIN}; + + ssl_certificate /etc/ssl/certs/${DOMAIN}/fullchain.pem; + ssl_certificate_key /etc/ssl/certs/${DOMAIN}/privkey.pem; + + root /var/www/html/${DOMAIN}; + index index.html; + + location .well-known/acme-challenge { + proxy_pass ${ACME_CHALLENGE_URL}; + } +} \ No newline at end of file diff --git a/scripts/reload.sh b/scripts/reload.sh new file mode 100644 index 0000000..9b92111 --- /dev/null +++ b/scripts/reload.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -euo pipefail + +nginx -s reload \ No newline at end of file diff --git a/scripts/watch-reload.sh b/scripts/watch-reload.sh new file mode 100644 index 0000000..d59831b --- /dev/null +++ b/scripts/watch-reload.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +set -euo pipefail + +touch "$RELOAD_FILE" + +# The loop will run once every time the file is saved. +while inotifywait -e close_write "$RELOAD_FILE"; do + echo "File '$RELOAD_FILE' changed. Reloading." + if nginx -t; then + nginx -s reload + else + echo "Nginx configuration is invalid. Skipping reload." + fi +done \ No newline at end of file