diff --git a/Dockerfile b/Dockerfile index 69fcb5f..ac4a995 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,8 @@ +FROM denoland/deno:alpine AS frontend +WORKDIR /app +COPY frontend/ ./ +RUN deno task build + FROM rust:1.92-alpine AS builder RUN apk add --no-cache ca-certificates musl-dev pkgconfig openssl-dev openssl-libs-static WORKDIR /app @@ -18,6 +23,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ FROM alpine:3.23 RUN apk add --no-cache msmtp ca-certificates && ln -sf /usr/bin/msmtp /usr/sbin/sendmail COPY --from=builder /tmp/tranquil-pds /usr/local/bin/tranquil-pds +COPY --from=frontend /app/dist /var/lib/tranquil-pds/frontend COPY migrations /app/migrations WORKDIR /app ENV SERVER_HOST=0.0.0.0 diff --git a/docker-compose.prod.yaml b/docker-compose.prod.yaml index 64c24fb..9d75e9d 100644 --- a/docker-compose.prod.yaml +++ b/docker-compose.prod.yaml @@ -27,25 +27,6 @@ services: reservations: memory: 256M - frontend: - build: - context: ./frontend - dockerfile: Dockerfile - image: tranquil-pds-frontend:latest - restart: unless-stopped - healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://localhost:80/"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 5s - deploy: - resources: - limits: - memory: 128M - reservations: - memory: 32M - db: image: postgres:18-alpine restart: unless-stopped @@ -76,12 +57,11 @@ services: - "80:80" - "443:443" volumes: - - ./nginx.frontend.conf:/etc/nginx/nginx.conf:ro + - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro - acme_challenge:/var/www/acme:ro depends_on: - tranquil-pds - - frontend healthcheck: test: ["CMD", "nginx", "-t"] interval: 30s diff --git a/docs/install-containers.md b/docs/install-containers.md index f892e61..0c455b4 100644 --- a/docs/install-containers.md +++ b/docs/install-containers.md @@ -145,7 +145,7 @@ Optional quadlets for valkey and minio are also available in `deploy/quadlets/` ## Create nginx configuration ```bash -cp /opt/tranquil-pds/nginx.frontend.conf /srv/tranquil-pds/config/nginx.conf +cp /opt/tranquil-pds/nginx.conf /srv/tranquil-pds/config/nginx.conf ``` ## Clone and build images @@ -288,7 +288,7 @@ openssl rand -base64 48 Copy the production compose and nginx configs: ```sh cp /opt/tranquil-pds/docker-compose.prod.yaml /srv/tranquil-pds/docker-compose.yml -cp /opt/tranquil-pds/nginx.frontend.conf /srv/tranquil-pds/config/nginx.conf +cp /opt/tranquil-pds/nginx.conf /srv/tranquil-pds/config/nginx.conf ``` Edit `/srv/tranquil-pds/docker-compose.yml` to adjust paths if needed: diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..cf61c3e --- /dev/null +++ b/nginx.conf @@ -0,0 +1,101 @@ +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 4096; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript + application/xml application/xml+rss text/javascript application/activity+json; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + + upstream pds { + server tranquil-pds:3000; + keepalive 32; + } + + server { + listen 80; + listen [::]:80; + server_name _; + + location /.well-known/acme-challenge/ { + root /var/www/acme; + } + + location / { + return 301 https://$host$request_uri; + } + } + + server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name _; + + ssl_certificate /etc/nginx/certs/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/privkey.pem; + + client_max_body_size 10G; + + proxy_http_version 1.1; + 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; + + location /xrpc/ { + proxy_pass http://pds; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 86400; + proxy_send_timeout 86400; + proxy_buffering off; + proxy_request_buffering off; + } + + location /oauth/ { + proxy_pass http://pds; + proxy_read_timeout 300; + proxy_send_timeout 300; + } + + location / { + proxy_pass http://pds; + } + } +} diff --git a/nginx.frontend.conf b/nginx.frontend.conf deleted file mode 100644 index 52ed905..0000000 --- a/nginx.frontend.conf +++ /dev/null @@ -1,179 +0,0 @@ -worker_processes auto; -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - -events { - worker_connections 4096; - use epoll; - multi_accept on; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for" ' - 'rt=$request_time uct="$upstream_connect_time" ' - 'uht="$upstream_header_time" urt="$upstream_response_time"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - gzip on; - gzip_vary on; - gzip_proxied any; - gzip_comp_level 6; - gzip_types text/plain text/css text/xml application/json application/javascript - application/xml application/xml+rss text/javascript application/activity+json; - - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; - ssl_prefer_server_ciphers off; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 1d; - ssl_session_tickets off; - ssl_stapling on; - ssl_stapling_verify on; - - upstream backend { - server tranquil-pds:3000; - keepalive 32; - } - - upstream frontend { - server frontend:80; - keepalive 16; - } - - server { - listen 80; - listen [::]:80; - server_name _; - - location /.well-known/acme-challenge/ { - root /var/www/acme; - } - - location / { - return 301 https://$host$request_uri; - } - } - - server { - listen 443 ssl; - listen [::]:443 ssl; - http2 on; - server_name _; - - ssl_certificate /etc/nginx/certs/fullchain.pem; - ssl_certificate_key /etc/nginx/certs/privkey.pem; - - client_max_body_size 10G; - - location /xrpc/ { - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - 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_read_timeout 86400; - proxy_send_timeout 86400; - proxy_buffering off; - proxy_request_buffering off; - } - - location = /oauth-client-metadata.json { - proxy_pass http://frontend; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header Accept-Encoding ""; - sub_filter_once off; - sub_filter_types application/json; - sub_filter '__FRONTEND_HOSTNAME__' $host; - } - - location /oauth/ { - proxy_pass http://backend; - proxy_http_version 1.1; - 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_read_timeout 300; - proxy_send_timeout 300; - } - - location /.well-known/ { - proxy_pass http://backend; - proxy_http_version 1.1; - 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; - } - - location /webhook/ { - proxy_pass http://backend; - proxy_http_version 1.1; - 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; - } - - location = /metrics { - proxy_pass http://backend; - proxy_http_version 1.1; - 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; - } - - location = /health { - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Host $host; - } - - location = /robots.txt { - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Host $host; - } - - location = /logo { - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Host $host; - } - - location ~ ^/u/[^/]+/did\.json$ { - proxy_pass http://backend; - proxy_http_version 1.1; - 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; - } - - location / { - proxy_pass http://frontend; - proxy_http_version 1.1; - 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; - } - } -}