diff --git a/README.md b/README.md index f744a91..ba88e7e 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,28 @@ Haskell modules in `src`: - `Vervis.Client` - `Vervis.Ssh` +### Testing the production image + +When Vervis is built in development mode (the flag is set in `stack.yml` and +can be overriden when running `stack build`), it works with plain HTTP, but in +production mode (e.g. when using the official prebuilt image), it requires +HTTPS, a domain, and the standard HTTP(S) ports. + +Instructions for testing a production image, assuming we'll use `dev.example` +as the server's domain name: + +1. The usual `./prepare-state.sh` and + `cp config/settings-sample-prod.yml config/settings.yml` +2. On the host system, edit `/etc/hosts`, adding the line + `127.0.0.1 dev.example` +3. In `config/settings.yml`, set the domain to `dev.example` +4. Run `./create-self-cert.sh` to generate a self-signed certificate for + `dev.example` +5. In `docker-compose.yml`, uncomment the `nginx` service +6. In `nginx.conf`, update the domain to `dev.example` and uncomment the + self-signed cert paths +7. `docker-compose up` + ## License AGPLv3+. See `COPYING`. diff --git a/create-self-cert.sh b/create-self-cert.sh new file mode 100755 index 0000000..8d85f74 --- /dev/null +++ b/create-self-cert.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout nginx-selfsigned.key \ + -out nginx-selfsigned.crt diff --git a/docker-compose.yml b/docker-compose.yml index b3657d9..08c5e65 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: # You can uncomment the following line if you want to not use the prebuilt # image, for example if you have local code changes #build: . - image: codeberg.org/forgefed/vervis:v0.1 + image: codeberg.org/forgefed/vervis:0.1 restart: always command: ./vervis config/settings.yml > log/vervis.log 2>&1 networks: @@ -36,6 +36,24 @@ services: - ./state:/app/state - ./config:/app/config + # The prebuilt production Vervis image requires HTTPS, a domain and standard + # ports, so if you want to test that image, NGINX can help + #nginx: + # image: nginx:bookworm + # restart: always + # depends_on: + # - web + # networks: + # - external_network + # - internal_network + # ports: + # - '127.0.0.1:80:80' + # - '127.0.0.1:443:443' + # volumes: + # - ./nginx.conf:/etc/nginx/conf.d/default.conf + # - ./nginx-selfsigned.key:/etc/ssl/private/nginx-selfsigned.key + # - ./nginx-selfsigned.crt:/etc/ssl/certs/nginx-selfsigned.crt + networks: external_network: internal_network: diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..35adf7d --- /dev/null +++ b/nginx.conf @@ -0,0 +1,85 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream backend { + server web:3000 fail_timeout=0; +} + +proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g; + +server { + listen 80; + listen [::]:80; + server_name dev.example; + location /.well-known/acme-challenge/ { allow all; } + location / { return 301 https://$host$request_uri; } +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name dev.example; + + ssl_protocols TLSv1.2 TLSv1.3; + + # You can use https://ssl-config.mozilla.org/ to generate your cipher set. + # We recommend their "Intermediate" level. + 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 on; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + + # Uncomment these lines once you acquire a certificate: + # ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; + # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; + + # Or uncomment these lines to use a celf-signed certificate: + # ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt; + # ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key; + + keepalive_timeout 70; + sendfile on; + client_max_body_size 99m; + + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml image/x-icon; + + location / { + try_files $uri @proxy; + } + + location @proxy { + 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; + proxy_set_header Proxy ""; + proxy_pass_header Server; + + proxy_pass http://backend; + proxy_buffering on; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + proxy_cache CACHE; + proxy_cache_valid 200 7d; + proxy_cache_valid 410 24h; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; + add_header X-Cached $upstream_cache_status; + + tcp_nodelay on; + } + + error_page 404 500 501 502 503 504 /500.html; +}