diff --git a/.env.example b/.env.example index bd384ee78d..5cc9632be0 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,18 @@ USE_HTTPS=true DOMAIN=your.domain.here EMAIL=your@email.here +# If you use letsencrypt and get https directly to nginx, use https mode, +# if you have something in front of nginx, like traefik/ngrok/apache that handles cerfiticates and +# you want to pass just http to nginx, use reverse_proxy mode. Same for local development, you can +# use reverse_proxy config to allow testing/development without ssl certificate. +# +# if you define PORT, it is used as what port is mapped to http port. In case you need to redefine +# https port, you need to modify docker-compose.yml or add override file for port-mapping yourself. +# +# If this is not defined, we default to https mode +# +# docker compose NGINX setup: https, reverse_proxy +NGINX_SETUP=https # Instance default language (see options at bookwyrm/settings.py "LANGUAGES" LANGUAGE_CODE="en-us" @@ -18,7 +30,9 @@ DEFAULT_LANGUAGE="English" # Specify when the site is served from a port that is not the default # for the protocol (80 for HTTP or 443 for HTTPS). -# Probably only necessary in development. +# +# This tells what port to listen for example reverse_proxy mode, you shouldn't need to set it other +# cases. # PORT=1333 STATIC_ROOT=static/ diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 6da6f4bae0..dbc4fefc8f 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -361,7 +361,13 @@ CSRF_COOKIE_SECURE = True PORT = env.int("PORT", 443 if USE_HTTPS else 80) -if (USE_HTTPS and PORT == 443) or (not USE_HTTPS and PORT == 80): + +# If we are behind reverse_proxy, we can assume that protocol://domain should point to correct webserver that routes to our nginx +if ( + (USE_HTTPS and PORT == 443) + or (not USE_HTTPS and PORT == 80) + or (env("NGINX_SETUP", "https") == "reverse_proxy") +): NETLOC = DOMAIN else: NETLOC = f"{DOMAIN}:{PORT}" diff --git a/bw-dev b/bw-dev index f96474df13..7821570a98 100755 --- a/bw-dev +++ b/bw-dev @@ -93,6 +93,9 @@ case "$CMD" in initdb) initdb "$@" ;; + init_ssl) + $DOCKER_COMPOSE --file ./docker-compose-init_letsencrypt.yml up --exit-code-from=certbot + ;; resetdb) prod_error $DOCKER_COMPOSE rm -svf diff --git a/docker-compose-init_letsencrypt.yml b/docker-compose-init_letsencrypt.yml new file mode 100644 index 0000000000..4bbe856c9e --- /dev/null +++ b/docker-compose-init_letsencrypt.yml @@ -0,0 +1,28 @@ +services: + nginx: + image: nginx:1.25.2 + ports: + - "80:80" + - "443:443" + networks: + - main + environment: + - DOMAIN=${DOMAIN} + volumes: + - ./nginx/locations:/etc/nginx/conf.d/locations + - ./nginx/server_config:/etc/nginx/conf.d/server_config + - ./nginx/server_name:/etc/nginx/conf.d/server_name + - ./nginx/ssl_bootstrap:/etc/nginx/templates/default.conf.template + - ./certbot/conf:/etc/nginx/ssl + - ./certbot/data:/var/www/certbot + certbot: + image: certbot/certbot:latest + command: certonly --webroot --webroot-path=/var/www/certbot --keep-until-expiring --email ${EMAIL} --agree-tos --no-eff-email -d ${DOMAIN} + depends_on: + - nginx + volumes: + - ./certbot/conf:/etc/letsencrypt + - ./certbot/logs:/var/log/letsencrypt + - ./certbot/data:/var/www/certbot +networks: + main: diff --git a/docker-compose.yml b/docker-compose.yml index af0007e2cb..dd552f7dd8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,15 +3,35 @@ services: image: nginx:1.25.2 restart: unless-stopped ports: - - "1333:80" + - "${PORT:-80}:80" + - "${PORT:-443:443}" depends_on: - web networks: - main + environment: + - DOMAIN=${DOMAIN} volumes: - - ./nginx:/etc/nginx/conf.d + - ./nginx/${NGINX_SETUP:-https}.conf:/etc/nginx/templates/default.conf.template + - ./nginx/locations:/etc/nginx/conf.d/locations + - ./nginx/server_config:/etc/nginx/conf.d/server_config + - ./nginx/server_name:/etc/nginx/conf.d/server_name + - ./nginx/99-autoreload.sh:/docker-entrypoint.d/99-autoreload.sh + - ./certbot/conf:/etc/nginx/ssl + - ./certbot/data:/var/www/certbot - static_volume:/app/static - media_volume:/app/images + certbot: + image: certbot/certbot:latest + entrypoint: /bin/sh -c "trap exit TERM; while [ "$NGINX_SETUP" = "https" ];do certbot renew --webroot --webroot-path=/var/www/certbot; sleep 1d & wait $${!}; done" + environment: + - USE_HTTPS=${USE_HTTPS} + depends_on: + - nginx + volumes: + - ./certbot/conf:/etc/letsencrypt + - ./certbot/logs:/var/log/letsencrypt + - ./certbot/data:/var/www/certbot db: image: postgres:13 env_file: .env diff --git a/nginx/99-autoreload.sh b/nginx/99-autoreload.sh new file mode 100755 index 0000000000..1977ab52bd --- /dev/null +++ b/nginx/99-autoreload.sh @@ -0,0 +1,5 @@ +#!/bin/bash +while true; do + sleep 1d + nginx -t && nginx -s reload +done & diff --git a/nginx/https.conf b/nginx/https.conf new file mode 100644 index 0000000000..99d4766d63 --- /dev/null +++ b/nginx/https.conf @@ -0,0 +1,84 @@ +include /etc/nginx/conf.d/server_config; + +upstream web { + server web:8000; +} +upstream flower{ + server flower:8888; +} + +server { + listen [::]:80; + listen 80; + + include /etc/nginx/conf.d/server_name; + + location ~ /.well-known/acme-challenge { + allow all; + root /var/www/certbot; + } + + # redirect http to https + return 301 https://${DOMAIN}$request_uri; +} + + +server { + access_log /var/log/nginx/access.log cache_log; + + listen [::]:443 ssl; + listen 443 ssl; + + http2 on; + + include /etc/nginx/conf.d/server_name; + + client_max_body_size 3M; + + if ($host != "${DOMAIN}") { + return 301 $scheme://${DOMAIN}$request_uri; + } + + # SSL code + ssl_certificate /etc/nginx/ssl/live/${DOMAIN}/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/live/${DOMAIN}/privkey.pem; + + location ~ /.well-known/acme-challenge { + allow all; + root /var/www/certbot; + } + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + #include /etc/nginx/mime.types; + #default_type application/octet-stream; + + gzip on; + gzip_disable "msie6"; + + proxy_read_timeout 1800s; + chunked_transfer_encoding on; + + # store responses to anonymous users for up to 1 minute + proxy_cache bookwyrm_cache; + proxy_cache_valid any 1m; + add_header X-Cache-Status $upstream_cache_status; + + # ignore the set cookie header when deciding to + # store a response in the cache + proxy_ignore_headers Cache-Control Set-Cookie Expires; + + # PUT requests always bypass the cache + # logged in sessions also do not populate the cache + # to avoid serving personal data to anonymous users + proxy_cache_methods GET HEAD; + proxy_no_cache $cookie_sessionid; + proxy_cache_bypass $cookie_sessionid; + + include /etc/nginx/conf.d/locations; + +} + diff --git a/nginx/production b/nginx/production deleted file mode 100644 index fb71c7e2c0..0000000000 --- a/nginx/production +++ /dev/null @@ -1,82 +0,0 @@ -include /etc/nginx/conf.d/server_config; - -upstream web { - server web:8000; -} -upstream flower{ - server flower:8888; -} - -server { - listen [::]:80; - listen 80; - - server_name your-domain.com www.your-domain.com; - - location ~ /.well-known/acme-challenge { - allow all; - root /var/www/certbot; - } - -# # redirect http to https -# return 301 https://your-domain.com$request_uri; -} - - -# server { -# access_log /var/log/nginx/access.log cache_log; -# -# listen [::]:443 ssl http2; -# listen 443 ssl http2; -# -# server_name your-domain.com; -# -# client_max_body_size 3M; -# -# if ($host != "your-domain.com") { -# return 301 $scheme://your-domain.com$request_uri; -# } -# -# # SSL code -# ssl_certificate /etc/nginx/ssl/live/your-domain.com/fullchain.pem; -# ssl_certificate_key /etc/nginx/ssl/live/your-domain.com/privkey.pem; -# -# location ~ /.well-known/acme-challenge { -# allow all; -# root /var/www/certbot; -# } -# -# sendfile on; -# tcp_nopush on; -# tcp_nodelay on; -# keepalive_timeout 65; -# types_hash_max_size 2048; -# #include /etc/nginx/mime.types; -# #default_type application/octet-stream; -# -# gzip on; -# gzip_disable "msie6"; -# -# proxy_read_timeout 1800s; -# chunked_transfer_encoding on; -# -# # store responses to anonymous users for up to 1 minute -# proxy_cache bookwyrm_cache; -# proxy_cache_valid any 1m; -# add_header X-Cache-Status $upstream_cache_status; -# -# # ignore the set cookie header when deciding to -# # store a response in the cache -# proxy_ignore_headers Cache-Control Set-Cookie Expires; -# -# # PUT requests always bypass the cache -# # logged in sessions also do not populate the cache -# # to avoid serving personal data to anonymous users -# proxy_cache_methods GET HEAD; -# proxy_no_cache $cookie_sessionid; -# proxy_cache_bypass $cookie_sessionid; -# -# include /etc/nginx/conf.d/locations; -# -# } - diff --git a/nginx/reverse_proxy b/nginx/reverse_proxy deleted file mode 100644 index cfae0692d7..0000000000 --- a/nginx/reverse_proxy +++ /dev/null @@ -1,19 +0,0 @@ -include /etc/nginx/conf.d/server_config; - -upstream web { - server web:8000; -} - -upstream flower{ - server flower:8888; -} - -# Reverse-Proxy server -server { - listen [::]:8001; - listen 8001; - - server_name your-domain.com www.your-domain.com; - - include /etc/nginx/conf.d/locations; -} diff --git a/nginx/development b/nginx/reverse_proxy.conf similarity index 95% rename from nginx/development rename to nginx/reverse_proxy.conf index 26ef3a9353..57a1016213 100644 --- a/nginx/development +++ b/nginx/reverse_proxy.conf @@ -11,6 +11,9 @@ server { access_log /var/log/nginx/access.log cache_log; listen 80; + include /etc/nginx/conf.d/server_name; + + http2 on; sendfile on; tcp_nopush on; diff --git a/nginx/server_name b/nginx/server_name new file mode 100644 index 0000000000..6b6da37399 --- /dev/null +++ b/nginx/server_name @@ -0,0 +1 @@ +server_name ${DOMAIN}; diff --git a/nginx/ssl_bootstrap b/nginx/ssl_bootstrap new file mode 100644 index 0000000000..c94ca83878 --- /dev/null +++ b/nginx/ssl_bootstrap @@ -0,0 +1,11 @@ +server { + listen [::]:80; + listen 80; + + include /etc/nginx/conf.d/server_name; + + location ~ /.well-known/acme-challenge { + allow all; + root /var/www/certbot; + } +}