278 lines
9.9 KiB
Nix
278 lines
9.9 KiB
Nix
{ pkgs, lib, ... }:
|
|
let
|
|
zulip-includes = pkgs.stdenv.mkDerivation rec {
|
|
pname = "zulip-include";
|
|
version = "8.4";
|
|
|
|
src = pkgs.fetchFromGitHub {
|
|
owner = "zulip";
|
|
repo = "zulip";
|
|
rev = version;
|
|
hash = "sha256-wsdVlJ0RDWsKwpvT0TsqqT8v5bubjsPDaGebRiIoQoQ=";
|
|
};
|
|
installPhase = ''
|
|
mkdir $out
|
|
cp puppet/zulip/files/nginx/{dhparam.pem,uwsgi_params,zulip-include-frontend/*,zulip-include-common/*} $out
|
|
sed -i "s/\/etc\/nginx\/zulip-include/\/nix\/store\/$(basename $out)/" $out/*
|
|
'';
|
|
};
|
|
host = "son.katvayor.net";
|
|
home = "/var/lib/zulip";
|
|
loadbalancers = [ ];
|
|
in
|
|
{
|
|
# nginx.pp
|
|
services.nginx = {
|
|
enable = true;
|
|
recommendedGzipSettings = true;
|
|
sslDhparam = zulip-includes + "/dhparam.pem";
|
|
upstreams = {
|
|
django.servers."unix:${home}/deployments/uwsgi-socket" = { };
|
|
localhost_sso.servers."127.0.0.1:8888" = { };
|
|
camo.servers."127.0.0.1:9292" = { };
|
|
# TODO later : mess with tornado
|
|
tornado.servers."127.0.0.1:9800" = {
|
|
keepalive = 10000;
|
|
};
|
|
};
|
|
commonHttpConfig =
|
|
let
|
|
trusted-proto =
|
|
if loadbalancers == [ ] then
|
|
''
|
|
map $remote_addr $trusted_x_forwarded_proto {
|
|
default $scheme;
|
|
}
|
|
map $http_x_forwarded_for $x_proxy_misconfiguration {
|
|
default "";
|
|
"~." "No proxies configured in Zulip, but proxy headers detected from proxy at $remote_addr; see https://zulip.readthedocs.io/en/latest/production/reverse-proxies.html";
|
|
}
|
|
''
|
|
else
|
|
''''; # TODO later : loadbalancers
|
|
tornado_map = # TODO later : mess with tornado
|
|
''
|
|
map "" $tornado_server {
|
|
default http://tornado;
|
|
}
|
|
'';
|
|
|
|
in
|
|
''
|
|
${trusted-proto}
|
|
${tornado_map}
|
|
'';
|
|
virtualHosts.${host} = {
|
|
forceSSL = true;
|
|
enableACME = true;
|
|
extraConfig = ''
|
|
error_page 502 503 504 /static/webpack-bundles/5xx.html;
|
|
'';
|
|
locations = {
|
|
# app
|
|
"/local-static/" = {
|
|
alias = "${home}/local-static/";
|
|
};
|
|
"/static/" = {
|
|
alias = "${home}/prod-static/";
|
|
extraConfig = ''
|
|
include ${zulip-includes}/headers;
|
|
add_header Access-Control-Allow-Origin *;
|
|
add_header Timing-Allow-Origin *;
|
|
error_page 404 /django_static_404.html;
|
|
location ~ '\.[0-9a-f]{12}\.|[./][0-9a-f]{20}\.' {
|
|
include ${zulip-includes}/headers;
|
|
add_header Access-Control-Allow-Origin *;
|
|
add_header Timing-Allow-Origin *;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
}
|
|
'';
|
|
};
|
|
"/json/events" = {
|
|
extraConfig = ''
|
|
if ($request_method = 'OPTIONS') {
|
|
# add_header does not propagate into/out of blocks, so this
|
|
# include cannot be factored out
|
|
include ${zulip-includes}/headers;
|
|
add_header Allow 'OPTIONS, GET, DELETE' always;
|
|
return 204;
|
|
}
|
|
|
|
if ($request_method !~ ^(GET|DELETE)$ ) {
|
|
# add_header does not propagate into/out of blocks, so this
|
|
# include cannot be factored out
|
|
include ${zulip-includes}/headers;
|
|
add_header Allow 'OPTIONS, GET, DELETE' always;
|
|
return 405;
|
|
}
|
|
|
|
proxy_pass $tornado_server;
|
|
include ${zulip-includes}/proxy_longpolling;
|
|
'';
|
|
};
|
|
"/api/v1/events" = {
|
|
extraConfig = ''
|
|
if ($request_method = 'OPTIONS') {
|
|
include ${zulip-includes}/tornado_cors_headers;
|
|
add_header Allow 'OPTIONS, GET, DELETE' always;
|
|
return 204;
|
|
}
|
|
|
|
if ($request_method !~ ^(GET|DELETE)$ ) {
|
|
include ${zulip-includes}/headers;
|
|
add_header Allow 'OPTIONS, GET, DELETE' always;
|
|
return 405;
|
|
}
|
|
|
|
include ${zulip-includes}/tornado_cors_headers;
|
|
proxy_pass $tornado_server;
|
|
include ${zulip-includes}/proxy_longpolling;
|
|
'';
|
|
};
|
|
"~ ^/internal/tornado/(\\d+)(/.*)$" = {
|
|
extraConfig = ''
|
|
internal;
|
|
proxy_pass http://tornado$1$2$is_args$args;
|
|
include ${zulip-includes}/proxy_longpolling;
|
|
'';
|
|
};
|
|
"/" = {
|
|
extraConfig = ''
|
|
include ${zulip-includes}/uwsgi_params;
|
|
'';
|
|
};
|
|
"/thumbnail" = {
|
|
extraConfig = ''
|
|
include ${zulip-includes}/api_headers;
|
|
include ${zulip-includes}/uwsgi_params;
|
|
'';
|
|
};
|
|
"/avatar" = {
|
|
extraConfig = ''
|
|
include ${zulip-includes}/api_headers;
|
|
include ${zulip-includes}/uwsgi_params;
|
|
'';
|
|
};
|
|
"/user_uploads" = {
|
|
extraConfig = ''
|
|
include ${zulip-includes}/api_headers;
|
|
include ${zulip-includes}/uwsgi_params;
|
|
'';
|
|
};
|
|
"/api/" = {
|
|
extraConfig = ''
|
|
include ${zulip-includes}/api_headers;
|
|
include ${zulip-includes}/uwsgi_params;
|
|
'';
|
|
};
|
|
# uploads-internal
|
|
"~ ^/internal/s3/(?<s3_hostname>[^/\\s]+)/(?<s3_path>[^\\s]*)" = {
|
|
extraConfig = ''
|
|
internal;
|
|
include ${zulip-includes}/headers;
|
|
add_header Content-Security-Policy "default-src 'none'; media-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; object-src 'self'; plugin-types application/pdf;";
|
|
|
|
# The components of this path are originally double-URI-escaped
|
|
# (see zerver/view/upload.py). "location" matches are on
|
|
# unescaped values, which fills $s3_path with a properly
|
|
# single-escaped path to pass to the upstream server.
|
|
# (see associated commit message for more details)
|
|
set $download_url https://$s3_hostname/$s3_path;
|
|
proxy_set_header Host $s3_hostname;
|
|
proxy_ssl_name $s3_hostname;
|
|
proxy_ssl_server_name on;
|
|
|
|
# Strip off X-amz-cf-id header, which otherwise the request has to
|
|
# have been signed over, leading to signature mismatches.
|
|
proxy_set_header x-amz-cf-id "";
|
|
|
|
# Strip off any auth request headers which the Zulip client might
|
|
# have sent, as they will not work for S3, and will report an error due
|
|
# to the signed auth header we also provide.
|
|
proxy_set_header Authorization "";
|
|
proxy_set_header x-amz-security-token "";
|
|
|
|
# These headers are only valid if there is a body, but better to
|
|
# strip them to be safe.
|
|
proxy_set_header Content-Length "";
|
|
proxy_set_header Content-Type "";
|
|
proxy_set_header Content-MD5 "";
|
|
proxy_set_header x-amz-content-sha256 "";
|
|
proxy_set_header Expect "";
|
|
|
|
# Ensure that we only get _one_ of these response headers: the one
|
|
# that Django added, not the one from S3.
|
|
proxy_hide_header Cache-Control;
|
|
proxy_hide_header Expires;
|
|
proxy_hide_header Set-Cookie;
|
|
# We are _leaving_ S3 to provide Content-Type,
|
|
# Content-Disposition, and Accept-Ranges headers, which are the
|
|
# three remaining headers which nginx would also pass through from
|
|
# the first response. Django explicitly unsets the first, and
|
|
# does not set the latter two.
|
|
|
|
proxy_pass $download_url$is_args$args;
|
|
proxy_cache uploads;
|
|
# If the S3 response doesn't contain Cache-Control headers (which
|
|
# we don't expect it to) then we assume they are valid for a very
|
|
# long time. The size of the cache is controlled by
|
|
# `s3_disk_cache_size` and read frequency, set via
|
|
# `s3_cache_inactive_time`.
|
|
proxy_cache_valid 200 1y;
|
|
|
|
# We only include the requested content-disposition in the cache
|
|
# key, so that we cache "Content-Disposition: attachment"
|
|
# separately from the inline version.
|
|
proxy_cache_key $download_url$s3_disposition_cache_key;
|
|
'';
|
|
};
|
|
"/internal/local/uploads/" = {
|
|
extraConfig = ''
|
|
internal;
|
|
include ${zulip-includes}/headers;
|
|
add_header Content-Security-Policy "default-src 'none'; media-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; object-src 'self'; plugin-types application/pdf;";
|
|
|
|
# Django handles setting Content-Type, Content-Disposition, and Cache-Control.
|
|
|
|
alias ${home}/uploads/files/;
|
|
'';
|
|
};
|
|
"/internal/local/user_avatars/" = {
|
|
extraConfig = ''
|
|
internal;
|
|
include ${zulip-includes}/headers;
|
|
add_header Content-Security-Policy "default-src 'none' img-src 'self'";
|
|
include ${zulip-includes}/uploads.types;
|
|
alias ${home}/uploads/avatars/;
|
|
'';
|
|
};
|
|
# external_sso
|
|
"/accounts/login/sso/" = {
|
|
extraConfig = ''
|
|
proxy_pass https://localhost_sso;
|
|
include ${zulip-includes}/proxy;
|
|
'';
|
|
};
|
|
# camo
|
|
"/external_content/metrics" = {
|
|
extraConfig = ''
|
|
return 404;
|
|
'';
|
|
};
|
|
"/external_content/" = {
|
|
extraConfig = ''
|
|
rewrite /external_content/(.*) /$1 break;
|
|
proxy_pass http://camo;
|
|
include ${zulip-includes}/proxy;
|
|
'';
|
|
};
|
|
|
|
"/health/" = {
|
|
extraConfig = lib.concatStringsSep "\n" (
|
|
map (host: "accept ${host};") loadbalancers ++ [ "deny all;" ]
|
|
);
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|