This commit is contained in:
catvayor 2024-07-05 18:09:54 +02:00
parent dc4d777962
commit 4a08968827
Signed by: lbailly
GPG key ID: CE3E645251AC63F3
4 changed files with 347 additions and 1 deletions

View file

@ -10,6 +10,7 @@
imports = [ imports = [
./hardware-configuration.nix ./hardware-configuration.nix
./disks.nix ./disks.nix
./zulip
]; ];
boot.loader.systemd-boot.enable = true; boot.loader.systemd-boot.enable = true;
@ -88,7 +89,6 @@
virtualHosts = { virtualHosts = {
"son.katvayor.net" = { "son.katvayor.net" = {
enableACME = true; enableACME = true;
addSSL = true;
}; };
}; };
}; };

View file

@ -0,0 +1,41 @@
{ pkgs, ... }:
let
home = "/var/lib/zulip";
in
{
imports = [ ./postgres.nix ./nginx.nix ];
# profile/base.pp
users = {
groups.zulip = { };
users.zulip = {
isSystemUser = true;
group = "zulip";
inherit home;
homeMode = "755";
};
};
systemd = {
tmpfiles.rules = [
''f ${home}/zulip.conf 644 zulip zulip ""''
''f ${home}/settings.py 644 zulip zulip ""''
''f ${home}/zulip-secrets.conf 640 zulip zulip ""''
];
};
#security.pam.loginLimits = [
# {
# domain = "zulip";
# type = "soft";
# item = "nofile";
# value = "1000000";
# }
# {
# domain = "zulip";
# type = "hard";
# item = "nofile";
# value = "1048576";
# }
#];
}

View file

@ -0,0 +1,278 @@
{ 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;" ]
);
};
};
};
};
}

View file

@ -0,0 +1,27 @@
{ ... }:
{
# postgresql_common.pp
users.users.postgres.extraGroups = [ "zulip" ];
# process_fts_updates.pp
systemd.services.process-fts-updates = {
script = ''''; # TODO : puppet/zulip/files/postgresql/process_fts_updates
serviceConfig = {
User = "zulip";
Group = "zulip";
};
};
# postgresql_base.pp
services.nagios.plugins = [
# TODO : puppet/zulip/files/nagios_plugins/zulip_postgresql
];
services.postgresql = {
enable = true;
ensureUsers = [
{
name = "zulip";
ensureDBOwnership = true;
}
];
ensureDatabases = [ "zulip" ];
};
}