~
This commit is contained in:
parent
dc4d777962
commit
4a08968827
4 changed files with 347 additions and 1 deletions
|
@ -10,6 +10,7 @@
|
|||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./disks.nix
|
||||
./zulip
|
||||
];
|
||||
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
|
@ -88,7 +89,6 @@
|
|||
virtualHosts = {
|
||||
"son.katvayor.net" = {
|
||||
enableACME = true;
|
||||
addSSL = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
41
machines/kat-son/zulip/default.nix
Normal file
41
machines/kat-son/zulip/default.nix
Normal 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";
|
||||
# }
|
||||
#];
|
||||
}
|
278
machines/kat-son/zulip/nginx.nix
Normal file
278
machines/kat-son/zulip/nginx.nix
Normal 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;" ]
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
27
machines/kat-son/zulip/postgres.nix
Normal file
27
machines/kat-son/zulip/postgres.nix
Normal 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" ];
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue