snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
2
puppet/zulip/files/apache/ports.conf
Normal file
2
puppet/zulip/files/apache/ports.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is empty since we want nginx to serve ports 80 and 443 on
|
||||
# this system.
|
||||
50
puppet/zulip/files/apache/sites/zulip-sso.example
Normal file
50
puppet/zulip/files/apache/sites/zulip-sso.example
Normal file
@@ -0,0 +1,50 @@
|
||||
# Copyright © 2013 Zulip, Inc. All rights reserved.
|
||||
|
||||
# Copy, modify, and enable this site if you need to use an Apache httpd module
|
||||
# for user authentication.
|
||||
|
||||
# Requests to /accounts/login/sso/ will be passed unchanged by the primary
|
||||
# Zulip web server to Apache. Once a request is authenticated, the REMOTE_USER
|
||||
# environment variable should be set by your authentication module.
|
||||
|
||||
# The config below will then invoke the Zulip web app under Apache, which will
|
||||
# detect the presence of the variable, log the user in, and then redirect back
|
||||
# to the app (served by the primary web server). You should configure your
|
||||
# authentication module below.
|
||||
|
||||
NameVirtualHost 127.0.0.1:8888
|
||||
Listen 127.0.0.1:8888
|
||||
|
||||
<VirtualHost 127.0.0.1:8888>
|
||||
# As an example, we've configured this service to use HTTP basic auth.
|
||||
# Insert the configuration for your SSO authentication module here:
|
||||
<Location />
|
||||
AuthType basic
|
||||
AuthName "zulip.example.com"
|
||||
AuthUserFile /home/zulip/zpasswd
|
||||
Require valid-user
|
||||
</Location>
|
||||
|
||||
ServerAdmin webmaster@localhost
|
||||
|
||||
ServerSignature Off
|
||||
|
||||
# You shouldn't need to edit anything below this line.
|
||||
|
||||
SSLEngine On
|
||||
SSLCertificateFile /etc/ssl/certs/zulip.combined-chain.crt
|
||||
SSLCertificateKeyFile /etc/ssl/private/zulip.key
|
||||
|
||||
WSGIScriptAlias / /home/zulip/deployments/current/zproject/wsgi.py
|
||||
WSGIDaemonProcess zulip threads=5 user=zulip python-path=/home/zulip/deployments/current/
|
||||
WSGIProcessGroup zulip
|
||||
WSGIApplicationGroup %{GLOBAL}
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/zulip_auth_error.log
|
||||
|
||||
# Possible values include: debug, info, notice, warn, error, crit,
|
||||
# alert, emerg.
|
||||
LogLevel warn
|
||||
|
||||
CustomLog ${APACHE_LOG_DIR}/zulip_auth_access.log combined
|
||||
</VirtualHost>
|
||||
3
puppet/zulip/files/cron.d/email-mirror
Normal file
3
puppet/zulip/files/cron.d/email-mirror
Normal file
@@ -0,0 +1,3 @@
|
||||
MAILTO=root
|
||||
|
||||
* * * * * zulip cd /home/zulip/deployments/current && ./manage.py email_mirror
|
||||
45
puppet/zulip/files/hooks/common/sentry.sh
Executable file
45
puppet/zulip/files/hooks/common/sentry.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
if ! grep -Eq 'SENTRY_DSN|SENTRY_FRONTEND_DSN' /etc/zulip/settings.py; then
|
||||
echo "sentry: No DSN configured! Set SENTRY_DSN or SENTRY_FRONTEND_DSN in /etc/zulip/settings.py"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! SENTRY_AUTH_TOKEN=$(crudini --get /etc/zulip/zulip-secrets.conf secrets sentry_release_auth_token); then
|
||||
echo "sentry: No release auth token set! Set sentry_release_auth_token in /etc/zulip/zulip-secrets.conf"
|
||||
exit 0
|
||||
fi
|
||||
export SENTRY_AUTH_TOKEN
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
if ! sentry_org=$(crudini --get /etc/zulip/zulip.conf sentry organization); then
|
||||
echo "sentry: No organization set! Set sentry.organization in /etc/zulip/zulip.conf"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sentry_project=$(crudini --get /etc/zulip/zulip.conf sentry project)
|
||||
sentry_frontend_project=$(crudini --get /etc/zulip/zulip.conf sentry frontend_project)
|
||||
if [ -z "$sentry_project" ] && [ -z "$sentry_frontend_project" ]; then
|
||||
echo "sentry: No project set! Set sentry.project and/or sentry.frontend_project in /etc/zulip/zulip.conf"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "$sentry_project" ] && ! grep -q 'SENTRY_DSN' /etc/zulip/settings.py; then
|
||||
echo "sentry: sentry.project is set but SENTRY_DSN is not set in /etc/zulip/settings.py"
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$sentry_frontend_project" ] && ! grep -q 'SENTRY_FRONTEND_DSN' /etc/zulip/settings.py; then
|
||||
echo "sentry: sentry.frontend_project is set but SENTRY_FRONTEND_DSN is not set in /etc/zulip/settings.py"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! which sentry-cli >/dev/null; then
|
||||
echo "sentry: No sentry-cli installed!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
sentry_release="zulip-server@$ZULIP_NEW_VERSION"
|
||||
46
puppet/zulip/files/hooks/common/zulip_notify.sh
Normal file
46
puppet/zulip/files/hooks/common/zulip_notify.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
if ! zulip_api_key=$(crudini --get /etc/zulip/zulip-secrets.conf secrets zulip_release_api_key); then
|
||||
echo "zulip_notify: No zulip_release_api_key set! Set zulip_release_api_key in /etc/zulip/zulip-secrets.conf"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! zulip_notify_bot_email=$(crudini --get /etc/zulip/zulip.conf zulip_notify bot_email); then
|
||||
echo "zulip_notify: No zulip_notify.bot_email set in /etc/zulip/zulip.conf"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! zulip_notify_server=$(crudini --get /etc/zulip/zulip.conf zulip_notify server); then
|
||||
echo "zulip_notify: No zulip_notify.server set in /etc/zulip/zulip.conf"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! zulip_notify_stream=$(crudini --get /etc/zulip/zulip.conf zulip_notify stream); then
|
||||
echo "zulip_notify: No zulip_notify.stream set in /etc/zulip/zulip.conf"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
from=${ZULIP_OLD_MERGE_BASE_COMMIT:-$ZULIP_OLD_VERSION}
|
||||
to=${ZULIP_NEW_MERGE_BASE_COMMIT:-$ZULIP_NEW_VERSION}
|
||||
deploy_environment=$(crudini --get /etc/zulip/zulip.conf machine deploy_type || echo "development")
|
||||
# shellcheck disable=SC2034
|
||||
commit_count=$(git rev-list "${from}..${to}" | wc -l)
|
||||
|
||||
# Sending the notification is best-effort: a failure to deliver it
|
||||
# (e.g. the bot lacks posting permission, or the server is
|
||||
# unreachable) must never abort a deploy, so we swallow the error
|
||||
# after logging it.
|
||||
zulip_send() {
|
||||
if ! uv run --no-sync zulip-send \
|
||||
--site "$zulip_notify_server" \
|
||||
--user "$zulip_notify_bot_email" \
|
||||
--api-key "$zulip_api_key" \
|
||||
--stream "$zulip_notify_stream" \
|
||||
--subject "$deploy_environment deploy" \
|
||||
--message "$1"; then
|
||||
echo "zulip_notify: Failed to send deploy notification; continuing anyway."
|
||||
fi
|
||||
}
|
||||
21
puppet/zulip/files/hooks/post-deploy.d/push_git_ref.hook
Normal file
21
puppet/zulip/files/hooks/post-deploy.d/push_git_ref.hook
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
if [ -z "${ZULIP_NEW_MERGE_BASE_COMMIT:-}" ]; then
|
||||
echo "push_git_ref: Can't push because deploy is not from git!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! repo=$(crudini --get /etc/zulip/zulip.conf push_git_ref repo); then
|
||||
echo "push_git_ref: No repo set! Set push_git_ref.repo in /etc/zulip/zulip.conf"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! ref=$(crudini --get /etc/zulip/zulip.conf push_git_ref ref); then
|
||||
echo "push_git_ref: No ref set! Set push_git_ref.ref in /etc/zulip/zulip.conf"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git push -f "$repo" "$ZULIP_NEW_MERGE_BASE_COMMIT:$ref"
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
/home/zulip/deployments/current/manage.py send_zulip_update_announcements --automated --progress
|
||||
17
puppet/zulip/files/hooks/post-deploy.d/sentry.hook
Executable file
17
puppet/zulip/files/hooks/post-deploy.d/sentry.hook
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
source "$(dirname "$0")/../common/sentry.sh"
|
||||
|
||||
deploy_environment=$(crudini --get /etc/zulip/zulip.conf machine deploy_type || echo "development")
|
||||
|
||||
echo "sentry: Adding deploy of '$deploy_environment' and finalizing release"
|
||||
|
||||
sentry-cli releases --org="$sentry_org" deploys "$sentry_release" new \
|
||||
--env "$deploy_environment" \
|
||||
--started "$(stat -c %Y ./sentry-release)" \
|
||||
--finished "$(date +%s)" \
|
||||
--name "$(hostname --fqdn)"
|
||||
sentry-cli releases --org="$sentry_org" finalize "$sentry_release"
|
||||
10
puppet/zulip/files/hooks/post-deploy.d/zulip_notify.hook
Normal file
10
puppet/zulip/files/hooks/post-deploy.d/zulip_notify.hook
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
source "$(dirname "$0")/../common/zulip_notify.sh"
|
||||
|
||||
echo "zulip_notify: Sending notify of $from .. $to ($commit_count commits) for $deploy_environment to $zulip_notify_server"
|
||||
|
||||
zulip_send "Finished ${deploy_environment} deploy of [${commit_count} new commits](https://github.com/zulip/zulip/compare/${from}...${to}) on $(hostname)"
|
||||
35
puppet/zulip/files/hooks/pre-deploy.d/sentry.hook
Executable file
35
puppet/zulip/files/hooks/pre-deploy.d/sentry.hook
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
source "$(dirname "$0")/../common/sentry.sh"
|
||||
|
||||
echo "$sentry_release" >./sentry-release
|
||||
|
||||
echo "sentry: Creating release $sentry_release"
|
||||
|
||||
# sentry-cli only supports passing one project when making a new
|
||||
# release, and we want to possibly create more than once at once. Use
|
||||
# curl to make the API request.
|
||||
json=$(jq -nc '{version: $ARGS.named.version,
|
||||
projects: $ARGS.positional | map(select( . != ""))}' \
|
||||
--arg version "$sentry_release" \
|
||||
--args "$sentry_project" "$sentry_frontend_project")
|
||||
curl "https://sentry.io/api/0/organizations/$sentry_org/releases/" \
|
||||
-H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$json" \
|
||||
--silent -o /dev/null
|
||||
|
||||
merge_base="${ZULIP_NEW_MERGE_BASE_COMMIT:-}"
|
||||
if [ -n "$merge_base" ]; then
|
||||
old_merge_base="$ZULIP_OLD_MERGE_BASE_COMMIT"
|
||||
echo "sentry: Setting commit range based on merge-base to upstream of $merge_base"
|
||||
sentry-cli releases --org="$sentry_org" set-commits "$sentry_release" --commit="zulip/zulip@$old_merge_base..$merge_base"
|
||||
fi
|
||||
|
||||
if [ -n "$sentry_frontend_project" ]; then
|
||||
echo "sentry: Uploading sourcemaps"
|
||||
sentry-cli releases --org="$sentry_org" --project="$sentry_frontend_project" files "$sentry_release" upload-sourcemaps static/webpack-bundles/
|
||||
fi
|
||||
10
puppet/zulip/files/hooks/pre-deploy.d/zulip_notify.hook
Normal file
10
puppet/zulip/files/hooks/pre-deploy.d/zulip_notify.hook
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
source "$(dirname "$0")/../common/zulip_notify.sh"
|
||||
|
||||
echo "zulip_notify: Sending notify of $from .. $to ($commit_count commits) for $deploy_environment to $zulip_notify_server"
|
||||
|
||||
zulip_send "Starting ${deploy_environment} deploy of [${commit_count} new commits](https://github.com/zulip/zulip/compare/${from}...${to}) on $(hostname)"
|
||||
17
puppet/zulip/files/letsencrypt/020-symlink.sh
Executable file
17
puppet/zulip/files/letsencrypt/020-symlink.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
symlink_with_backup() {
|
||||
if [ -e "$2" ]; then
|
||||
# If the user is setting up our automatic certbot-management on a
|
||||
# system that already has certs for Zulip, use some extra caution
|
||||
# to keep the old certs available.
|
||||
mv -f --backup=numbered "$2" "$2".setup-certbot || true
|
||||
fi
|
||||
ln -nsf "$1" "$2"
|
||||
}
|
||||
|
||||
if [ -n "${ZULIP_DOMAIN:-}" ]; then
|
||||
CERT_DIR="/etc/letsencrypt/live/$ZULIP_DOMAIN"
|
||||
symlink_with_backup "$CERT_DIR/privkey.pem" /etc/ssl/private/zulip.key
|
||||
symlink_with_backup "$CERT_DIR/fullchain.pem" /etc/ssl/certs/zulip.combined-chain.crt
|
||||
fi
|
||||
3
puppet/zulip/files/letsencrypt/050-nginx.sh
Executable file
3
puppet/zulip/files/letsencrypt/050-nginx.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
service nginx reload
|
||||
4
puppet/zulip/files/letsencrypt/055-email-server.sh
Executable file
4
puppet/zulip/files/letsencrypt/055-email-server.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
supervisorctl restart zulip-email-server
|
||||
4
puppet/zulip/files/limits.d/zulip.conf
Normal file
4
puppet/zulip/files/limits.d/zulip.conf
Normal file
@@ -0,0 +1,4 @@
|
||||
zulip soft nofile 1000000
|
||||
zulip hard nofile 1048576
|
||||
root soft nofile 1000000
|
||||
root hard nofile 1048576
|
||||
10
puppet/zulip/files/logrotate/smokescreen
Normal file
10
puppet/zulip/files/logrotate/smokescreen
Normal file
@@ -0,0 +1,10 @@
|
||||
/var/log/zulip/smokescreen.log
|
||||
{
|
||||
missingok
|
||||
rotate 14
|
||||
daily
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
copytruncate
|
||||
}
|
||||
58
puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_cron_file
Executable file
58
puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_cron_file
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Nagios plugin paired with a cron job. This just verifies that the
|
||||
file output by the cron job is correct.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def nagios_from_file(results_file: str, max_time_diff: int = 60 * 2) -> tuple[int, str]:
|
||||
"""Returns a nagios-appropriate string and return code obtained by
|
||||
parsing the desired file on disk. The file on disk should be of format
|
||||
|
||||
%s|%s % (timestamp, nagios_string)
|
||||
|
||||
This file is created by various nagios checking cron jobs such as
|
||||
check-rabbitmq-queues and check-rabbitmq-consumers"""
|
||||
|
||||
try:
|
||||
with open(results_file) as f:
|
||||
data = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
state = "UNKNOWN"
|
||||
ret = 3
|
||||
data = "Results file is missing"
|
||||
else:
|
||||
pieces = data.split("|")
|
||||
|
||||
if len(pieces) != 4:
|
||||
state = "UNKNOWN"
|
||||
ret = 3
|
||||
data = "Results file malformed"
|
||||
else:
|
||||
timestamp = int(pieces[0])
|
||||
|
||||
time_diff = int(time.time() - timestamp)
|
||||
if time_diff > max_time_diff:
|
||||
ret = 3
|
||||
state = "UNKNOWN"
|
||||
data = "Results file is stale"
|
||||
else:
|
||||
ret = int(pieces[1])
|
||||
state = pieces[2]
|
||||
data = pieces[3]
|
||||
|
||||
return (ret, f"{state}: {data}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
RESULTS_FILE = sys.argv[1]
|
||||
extra_args = {}
|
||||
if len(sys.argv) > 2:
|
||||
extra_args["max_time_diff"] = int(sys.argv[2])
|
||||
ret, result = nagios_from_file(RESULTS_FILE, **extra_args)
|
||||
|
||||
print(result)
|
||||
sys.exit(ret)
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Nagios plugin to check that none of our queue workers have reported errors.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
# settings.QUEUE_ERROR_DIR; not importing Django so that this can run
|
||||
# as the nagios user.
|
||||
wildcard = os.path.join("/var/log/zulip/queue_error", "*.errors")
|
||||
clean = True
|
||||
for fn in glob.glob(wildcard):
|
||||
print(f"WARNING: Queue errors logged in {fn}")
|
||||
clean = False
|
||||
|
||||
if not clean:
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
148
puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time
Executable file
148
puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Script to provide information about send-receive times.
|
||||
|
||||
It must be run on a machine that is using the live database for the
|
||||
Django ORM.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from typing import Any, Literal, NoReturn
|
||||
|
||||
sys.path.append(".")
|
||||
sys.path.append("/home/zulip/deployments/current")
|
||||
from scripts.lib.setup_path import setup_path
|
||||
from scripts.lib.zulip_tools import atomic_nagios_write
|
||||
|
||||
setup_path()
|
||||
|
||||
import django
|
||||
import zulip
|
||||
|
||||
sys.path.append("/home/zulip/deployments/current")
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.settings"
|
||||
|
||||
django.setup()
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from zerver.models.realms import get_realm
|
||||
from zerver.models.users import get_system_bot
|
||||
from zproject.config import get_config
|
||||
|
||||
usage = """Usage: send-receive.py [options] [config]
|
||||
|
||||
'config' is optional, if present will return config info.
|
||||
Otherwise, returns the output data."""
|
||||
|
||||
parser = argparse.ArgumentParser(usage=usage)
|
||||
parser.add_argument("--site", default=f"https://{settings.NAGIOS_BOT_HOST}")
|
||||
parser.add_argument("--insecure", action="store_true")
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
|
||||
def report(
|
||||
state: Literal["ok", "warning", "critical", "unknown"],
|
||||
timestamp: float | None = None,
|
||||
msg: str | None = None,
|
||||
) -> NoReturn:
|
||||
if msg is None:
|
||||
msg = f"send time was {timestamp}"
|
||||
sys.exit(atomic_nagios_write("check_send_receive_state", state, msg))
|
||||
|
||||
|
||||
def send_zulip(sender: zulip.Client, message: dict[str, Any]) -> None:
|
||||
result = sender.send_message(message)
|
||||
if result["result"] != "success":
|
||||
report("critical", msg=f"Error sending Zulip, args were: {message}, {result}")
|
||||
|
||||
|
||||
def get_zulips() -> list[dict[str, Any]]:
|
||||
global last_event_id
|
||||
res = zulip_recipient.get_events(queue_id=queue_id, last_event_id=last_event_id)
|
||||
if "error" in res.get("result", {}):
|
||||
report("critical", msg="Error receiving Zulips, error was: {}".format(res["msg"]))
|
||||
for event in res["events"]:
|
||||
last_event_id = max(last_event_id, int(event["id"]))
|
||||
# If we get a heartbeat event, that means we've been hanging for
|
||||
# 40s, and we should bail.
|
||||
if "heartbeat" in (event["type"] for event in res["events"]):
|
||||
report("critical", msg="Got heartbeat waiting for Zulip, which means get_events is hanging")
|
||||
return [event["message"] for event in res["events"]]
|
||||
|
||||
|
||||
internal_realm_id = get_realm(settings.SYSTEM_BOT_REALM).id
|
||||
if (
|
||||
get_config("machine", "deploy_type") == "staging"
|
||||
and settings.NAGIOS_STAGING_SEND_BOT is not None
|
||||
and settings.NAGIOS_STAGING_RECEIVE_BOT is not None
|
||||
):
|
||||
sender = get_system_bot(settings.NAGIOS_STAGING_SEND_BOT, internal_realm_id)
|
||||
recipient = get_system_bot(settings.NAGIOS_STAGING_RECEIVE_BOT, internal_realm_id)
|
||||
else:
|
||||
sender = get_system_bot(settings.NAGIOS_SEND_BOT, internal_realm_id)
|
||||
recipient = get_system_bot(settings.NAGIOS_RECEIVE_BOT, internal_realm_id)
|
||||
|
||||
zulip_sender = zulip.Client(
|
||||
email=sender.email,
|
||||
api_key=sender.api_key,
|
||||
verbose=True,
|
||||
insecure=options.insecure,
|
||||
client="ZulipMonitoring/0.1",
|
||||
site=options.site,
|
||||
)
|
||||
|
||||
zulip_recipient = zulip.Client(
|
||||
email=recipient.email,
|
||||
api_key=recipient.api_key,
|
||||
verbose=True,
|
||||
insecure=options.insecure,
|
||||
client="ZulipMonitoring/0.1",
|
||||
site=options.site,
|
||||
)
|
||||
|
||||
try:
|
||||
res = zulip_recipient.register(event_types=["message"])
|
||||
if "error" in res.get("result", {}):
|
||||
report("critical", msg="Error subscribing to Zulips: {}".format(res["msg"]))
|
||||
queue_id, last_event_id = (res["queue_id"], res["last_event_id"])
|
||||
except Exception:
|
||||
report("critical", msg=f"Error subscribing to Zulips:\n{traceback.format_exc()}")
|
||||
msg_to_send = str(random.getrandbits(64))
|
||||
time_start = time.perf_counter()
|
||||
|
||||
send_zulip(
|
||||
zulip_sender,
|
||||
{
|
||||
"type": "private",
|
||||
"content": msg_to_send,
|
||||
"subject": "time to send",
|
||||
"to": recipient.email,
|
||||
},
|
||||
)
|
||||
|
||||
complete = False
|
||||
while not complete:
|
||||
messages = get_zulips()
|
||||
seconds_diff = time.perf_counter() - time_start
|
||||
for m in messages:
|
||||
if msg_to_send == m["content"]:
|
||||
zulip_sender.delete_message(m["id"])
|
||||
complete = True
|
||||
break
|
||||
|
||||
zulip_recipient.deregister(queue_id)
|
||||
|
||||
if seconds_diff > 12:
|
||||
report("critical", timestamp=seconds_diff)
|
||||
if seconds_diff > 3:
|
||||
report("warning", timestamp=seconds_diff)
|
||||
else:
|
||||
report("ok", timestamp=seconds_diff)
|
||||
24
puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_worker_memory
Executable file
24
puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_worker_memory
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Checks for any Zulip queue workers that are leaking memory and thus have a high vsize
|
||||
datafile=$(mktemp)
|
||||
|
||||
# We expect other Nagios checks to monitor for whether no queue
|
||||
# workers are running, so we give that condition a pass
|
||||
processes=$(pgrep -xf 'python.* /home/zulip/deployments/current/manage.py process_queue .*')
|
||||
if [ -z "$processes" ]; then
|
||||
echo "No workers running"
|
||||
exit 0
|
||||
fi
|
||||
mapfile -t processes <<<"$processes"
|
||||
ps -o vsize,size,pid,user,command --sort -vsize "${processes[@]}" >"$datafile"
|
||||
cat "$datafile"
|
||||
top_worker=$(head -n2 "$datafile" | tail -n1)
|
||||
top_worker_memory_usage=$(echo "$top_worker" | cut -f1 -d" ")
|
||||
rm -f "$datafile"
|
||||
if [ "$top_worker_memory_usage" -gt 800000 ]; then
|
||||
exit 2
|
||||
elif [ "$top_worker_memory_usage" -gt 600000 ]; then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
146
puppet/zulip/files/nagios_plugins/zulip_base/check_debian_packages
Executable file
146
puppet/zulip/files/nagios_plugins/zulip_base/check_debian_packages
Executable file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# check_debian_packages - nagios plugin
|
||||
#
|
||||
#
|
||||
# Copyright (C) 2005 Francesc Guasch
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# Report bugs to: frankie@etsetb.upc.edu
|
||||
#
|
||||
use strict;
|
||||
use lib '/usr/lib/nagios/plugins';
|
||||
use utils qw(%ERRORS &print_revision &support &usage);
|
||||
use Getopt::Long;
|
||||
|
||||
my $VERSION = '0.06';
|
||||
|
||||
my $RET = 'OK';
|
||||
my $LOCK_FILE = "/var/lib/dpkg/lock";
|
||||
my $CMD_APT = "/usr/bin/apt-get -s upgrade";
|
||||
my $TIMEOUT = 60;
|
||||
my $DEBUG = 0;
|
||||
|
||||
#####################################################################
|
||||
#
|
||||
# Command line arguments
|
||||
#
|
||||
|
||||
sub print_usage ();
|
||||
|
||||
my ($help,$version);
|
||||
GetOptions( help => \$help,
|
||||
debug => \$DEBUG,
|
||||
version => \$version,
|
||||
'timeout=s' => \$TIMEOUT
|
||||
);
|
||||
my ($PROGNAME) = $0 =~ m#.*/(.*)#;
|
||||
|
||||
if ($help) {
|
||||
print_revision($PROGNAME,"\$Revision: $VERSION \$");
|
||||
print "Copyright (c) 2005 Francesc Guasch - Ortiz
|
||||
|
||||
Perl Check debian packages plugin for Nagios
|
||||
|
||||
";
|
||||
print_usage();
|
||||
exit($ERRORS{OK});
|
||||
}
|
||||
|
||||
if ($version) {
|
||||
print_revision($PROGNAME,"\$Revision: $VERSION \$");
|
||||
exit($ERRORS{OK});
|
||||
}
|
||||
|
||||
#
|
||||
# unlikely but compliant
|
||||
#
|
||||
$SIG{'ALRM'} = sub {
|
||||
print ("ERROR: Timeout\n");
|
||||
exit $ERRORS{"UNKNOWN"};
|
||||
};
|
||||
alarm($TIMEOUT);
|
||||
|
||||
|
||||
######################################################################
|
||||
#
|
||||
# subs
|
||||
#
|
||||
|
||||
sub print_usage () {
|
||||
print "Usage: $PROGNAME [--debug] [--version] [--help]"
|
||||
." [--timeout=$TIMEOUT]\n";
|
||||
}
|
||||
|
||||
sub add_info {
|
||||
my ($info,$type,$pkg) = @_;
|
||||
$$info .= scalar(keys %$pkg)." new pkgs in $type: ";
|
||||
if (keys %$pkg< 5 ) {
|
||||
$$info .= join " ",keys %$pkg;
|
||||
} else {
|
||||
my $alguns = join " ",keys %$pkg;
|
||||
$alguns = substr($alguns,0,80);
|
||||
$alguns .= "...";
|
||||
$$info .= $alguns;
|
||||
}
|
||||
}
|
||||
|
||||
sub exit_unknown {
|
||||
my ($info) = @_;
|
||||
chomp $info;
|
||||
$RET='UNKNOWN';
|
||||
print "$RET: $info\n";
|
||||
exit $ERRORS{$RET};
|
||||
};
|
||||
|
||||
sub run_apt {
|
||||
my ($pkg,$ver,$type,$release);
|
||||
open APT,"$CMD_APT 2>&1|" or exit_unknown($!);
|
||||
my (%stable,%security,%other);
|
||||
while (<APT>) {
|
||||
print "APT: $_" if $DEBUG;
|
||||
exit_unknown($_) if /(Could not open lock file)|(Could not get lock)/;
|
||||
next unless /^Inst/;
|
||||
($pkg,$ver,$release) = /Inst (.*?) .*\((.*?) (.*?)\)/;
|
||||
print "$_\npkg=$pkg ver=$ver release=$release\n" if $DEBUG;
|
||||
die "$_\n" unless defined $release;
|
||||
$release = 'stable'
|
||||
if $release =~ /stable$/ && $release !~/security/i;
|
||||
$release = 'security'
|
||||
if $release =~ /security/i;
|
||||
if ($release eq 'stable') {
|
||||
$stable{$pkg} = $ver;
|
||||
} elsif ($release eq 'security') {
|
||||
$security{$pkg} = $ver;
|
||||
} else {
|
||||
$other{$pkg}=$ver;
|
||||
}
|
||||
}
|
||||
close APT;
|
||||
my $info = '';
|
||||
if (keys (%security)) {
|
||||
$RET = 'CRITICAL';
|
||||
add_info(\$info,'security',\%security);
|
||||
} elsif (keys (%other) or keys(%stable)) {
|
||||
$RET = 'WARNING';
|
||||
add_info(\$info,'stable',\%stable);
|
||||
add_info(\$info,'other',\%other) if keys %other;
|
||||
}
|
||||
print "$RET: $info\n";
|
||||
}
|
||||
|
||||
run_apt();
|
||||
exit $ERRORS{$RET};
|
||||
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Nagios plugin to check the difference between the primary and
|
||||
replica PostgreSQL servers' xlog location. Requires that the user this
|
||||
connects to PostgreSQL as has been granted the `pg_monitor` role.
|
||||
|
||||
This can only use stdlib modules from python.
|
||||
"""
|
||||
|
||||
import configparser
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def get_config(
|
||||
config_file: configparser.RawConfigParser,
|
||||
section: str,
|
||||
key: str,
|
||||
default_value: str = "",
|
||||
) -> str:
|
||||
if config_file.has_option(section, key):
|
||||
return config_file.get(section, key)
|
||||
return default_value
|
||||
|
||||
|
||||
config_file = configparser.RawConfigParser()
|
||||
config_file.read("/etc/zulip/zulip.conf")
|
||||
|
||||
states = {
|
||||
"OK": 0,
|
||||
"WARNING": 1,
|
||||
"CRITICAL": 2,
|
||||
"UNKNOWN": 3,
|
||||
}
|
||||
|
||||
MAXSTATE = 0
|
||||
|
||||
|
||||
def report(state: str, msg: str) -> None:
|
||||
global MAXSTATE
|
||||
print(f"{state}: {msg}")
|
||||
MAXSTATE = max(MAXSTATE, states[state])
|
||||
|
||||
|
||||
def run_sql_query(query: str) -> list[list[str]]:
|
||||
command = [
|
||||
"psql",
|
||||
"-t", # Omit header line
|
||||
"-A", # Don't pad with spaces
|
||||
"-z", # Separate columns with nulls
|
||||
"-v",
|
||||
"ON_ERROR_STOP=1",
|
||||
"-d",
|
||||
get_config(config_file, "postgresql", "database_name", "zulip"),
|
||||
# No -U; nagios connects as itself
|
||||
"-c",
|
||||
f"SELECT {query}",
|
||||
]
|
||||
try:
|
||||
output = subprocess.check_output(command, stderr=subprocess.STDOUT, text=True).strip()
|
||||
if not output:
|
||||
return []
|
||||
return [x.split("\0") for x in output.split("\n")]
|
||||
except subprocess.CalledProcessError as e:
|
||||
report("CRITICAL", f"psql failed: {e}: {e.output}")
|
||||
sys.exit(MAXSTATE)
|
||||
|
||||
|
||||
def loc_to_abs_offset(loc_str: str) -> int:
|
||||
m = re.match(r"^\s*([0-9a-fA-F]+)/([0-9a-fA-F]+)\s*$", loc_str)
|
||||
if not m:
|
||||
raise ValueError("Unknown xlog location format: " + loc_str)
|
||||
(xlog_file, file_offset) = (m.group(1), m.group(2))
|
||||
|
||||
# From PostgreSQL 9.2's pg_xlog_location_diff:
|
||||
# result = XLogFileSize * (xlogid1 - xlogid2) + xrecoff1 - xrecoff2
|
||||
# Taking xlogid2 and xrecoff2 to be zero to get the absolute offset:
|
||||
# result = XLogFileSize * xlogid1 + xrecoff1
|
||||
#
|
||||
# xlog_internal.h says:
|
||||
# #define XLogSegSize ((uint32) XLOG_SEG_SIZE)
|
||||
# #define XLogSegsPerFile (((uint32) 0xffffffff) / XLogSegSize)
|
||||
# #define XLogFileSize (XLogSegsPerFile * XLogSegSize)
|
||||
#
|
||||
# Since XLOG_SEG_SIZE is normally 16MB, XLogFileSize comes out to 0xFF000000
|
||||
return 0xFF000000 * int(xlog_file, 16) + int(file_offset, 16)
|
||||
|
||||
|
||||
is_in_recovery = run_sql_query("pg_is_in_recovery()")
|
||||
|
||||
if is_in_recovery[0][0] == "t":
|
||||
replication_info = run_sql_query(
|
||||
"sender_host, status, pg_last_wal_replay_lsn(), pg_last_wal_receive_lsn()"
|
||||
" from pg_stat_wal_receiver"
|
||||
)
|
||||
if not replication_info:
|
||||
report("CRITICAL", "Replaying WAL logs from backups")
|
||||
else:
|
||||
(primary_server, state, replay_loc, recv_loc) = replication_info[0]
|
||||
recv_offset = loc_to_abs_offset(recv_loc)
|
||||
replay_lag = recv_offset - loc_to_abs_offset(replay_loc)
|
||||
|
||||
if state != "streaming":
|
||||
report("CRITICAL", f"replica is in state {state}, not streaming")
|
||||
|
||||
msg = f"replica is {replay_lag} bytes behind in replay of WAL logs from {primary_server}"
|
||||
if replay_lag > 5 * 16 * 1024**2:
|
||||
report("CRITICAL", msg)
|
||||
elif replay_lag > 16 * 1024**2:
|
||||
report("WARNING", msg)
|
||||
else:
|
||||
report("OK", msg)
|
||||
|
||||
else:
|
||||
replication_info = run_sql_query(
|
||||
"client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn from pg_stat_replication"
|
||||
)
|
||||
if not replication_info:
|
||||
report("CRITICAL", "No replicas!")
|
||||
elif len(replication_info) == 1:
|
||||
report("WARNING", "Only one replica!")
|
||||
else:
|
||||
report("OK", f"Found {len(replication_info)} replicas")
|
||||
|
||||
for replica in replication_info:
|
||||
(client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn) = replica
|
||||
if state != "streaming":
|
||||
report("CRITICAL", f"replica {client_addr} is in state {state}, not streaming")
|
||||
|
||||
sent_offset = loc_to_abs_offset(sent_lsn)
|
||||
lag: dict[str, int] = {}
|
||||
lag["write"] = sent_offset - loc_to_abs_offset(write_lsn)
|
||||
lag["flush"] = sent_offset - loc_to_abs_offset(flush_lsn)
|
||||
lag["replay"] = sent_offset - loc_to_abs_offset(replay_lsn)
|
||||
for lag_type in ("write", "flush", "replay"):
|
||||
lag_bytes = lag[lag_type]
|
||||
msg = f"replica {client_addr} is {lag_bytes} bytes behind in {lag_type} of WAL logs"
|
||||
if lag_bytes > 5 * 16 * 1024**2:
|
||||
report("CRITICAL", msg)
|
||||
elif lag_bytes > 16 * 1024**2:
|
||||
report("WARNING", msg)
|
||||
else:
|
||||
report("OK", msg)
|
||||
|
||||
sys.exit(MAXSTATE)
|
||||
8
puppet/zulip/files/nginx/dhparam.pem
Normal file
8
puppet/zulip/files/nginx/dhparam.pem
Normal file
@@ -0,0 +1,8 @@
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
|
||||
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
|
||||
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
|
||||
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
|
||||
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
|
||||
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
|
||||
-----END DH PARAMETERS-----
|
||||
25
puppet/zulip/files/nginx/uwsgi_params
Normal file
25
puppet/zulip/files/nginx/uwsgi_params
Normal file
@@ -0,0 +1,25 @@
|
||||
uwsgi_param QUERY_STRING $query_string;
|
||||
uwsgi_param REQUEST_METHOD $request_method;
|
||||
uwsgi_param CONTENT_TYPE $content_type;
|
||||
uwsgi_param CONTENT_LENGTH $content_length;
|
||||
uwsgi_param REQUEST_URI $request_uri;
|
||||
uwsgi_param PATH_INFO $document_uri;
|
||||
uwsgi_param DOCUMENT_ROOT $document_root;
|
||||
uwsgi_param SERVER_PROTOCOL $server_protocol;
|
||||
uwsgi_param REQUEST_SCHEME $scheme;
|
||||
uwsgi_param HTTPS $https if_not_empty;
|
||||
uwsgi_param REMOTE_ADDR $trusted_remote_addr;
|
||||
uwsgi_param REMOTE_PORT $remote_port;
|
||||
uwsgi_param SERVER_ADDR $server_addr;
|
||||
uwsgi_param SERVER_PORT $server_port;
|
||||
uwsgi_param SERVER_NAME $server_name;
|
||||
uwsgi_param HTTP_X_REAL_IP $trusted_remote_addr;
|
||||
uwsgi_param HTTP_X_FORWARDED_PROTO $trusted_x_forwarded_proto;
|
||||
uwsgi_param HTTP_X_FORWARDED_SSL "";
|
||||
uwsgi_param HTTP_X_PROXY_MISCONFIGURATION $x_proxy_misconfiguration;
|
||||
|
||||
# This value is the default, and is provided for explicitness; it must
|
||||
# be longer than the configured 55s "harakiri" timeout in uwsgi
|
||||
uwsgi_read_timeout 60s;
|
||||
|
||||
uwsgi_pass django;
|
||||
11
puppet/zulip/files/nginx/zulip-include-app.d/camo.conf
Normal file
11
puppet/zulip/files/nginx/zulip-include-app.d/camo.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
# Proxies /external_content to a local installation of the camo image
|
||||
# proxy software. Because camo serves its metrics at /metrics,
|
||||
# explicitly 404 that to external requests.
|
||||
location /external_content/metrics {
|
||||
return 404;
|
||||
}
|
||||
location /external_content {
|
||||
rewrite /external_content/(.*) /$1 break;
|
||||
proxy_pass http://camo;
|
||||
include /etc/nginx/zulip-include/proxy;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Use this when you have a server listening on localhost that is serving Zulip
|
||||
# with some sort of authentication middleware installed. The proxied-to server
|
||||
# should pass REMOTE_USER to /accounts/login/sso/ once it has satisfactorily
|
||||
# identified the user.
|
||||
location /accounts/login/sso/ {
|
||||
proxy_pass https://localhost_sso;
|
||||
include /etc/nginx/zulip-include/proxy;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# This file should be used if the nginx is behind a dedicated
|
||||
# loadbalancer. This allows nginx to be configured for long
|
||||
# keep-alive timeouts, to keep connections open to the loadbalancer.
|
||||
#
|
||||
# All timeouts here should be _longer_ than those on the loadbalancer.
|
||||
# This ensures that there cannot be a race condition between nginx
|
||||
# deciding to shut down the connection, and the load-balancer sending
|
||||
# a request, resulting in a 502 from the loadbalancer.
|
||||
#
|
||||
# The timeouts on the loadbalancer are assumed to be 20min (1200s) or
|
||||
# less; these timeouts are hence for 21min.
|
||||
|
||||
keepalive_timeout 1260;
|
||||
client_header_timeout 1260;
|
||||
client_body_timeout 1260;
|
||||
|
||||
keepalive_requests 500;
|
||||
@@ -0,0 +1,4 @@
|
||||
include /etc/nginx/zulip-include/headers;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Headers Authorization always;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, DELETE, PUT, PATCH, HEAD' always;
|
||||
4
puppet/zulip/files/nginx/zulip-include-common/certbot
Normal file
4
puppet/zulip/files/nginx/zulip-include-common/certbot
Normal file
@@ -0,0 +1,4 @@
|
||||
# Directory needed for certbot --webroot to work.
|
||||
location /.well-known/acme-challenge/ {
|
||||
alias /var/lib/zulip/certbot-webroot/.well-known/acme-challenge/;
|
||||
}
|
||||
10
puppet/zulip/files/nginx/zulip-include-common/headers
Normal file
10
puppet/zulip/files/nginx/zulip-include-common/headers
Normal file
@@ -0,0 +1,10 @@
|
||||
# Enable HSTS: tell browsers to always use HTTPS
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
|
||||
|
||||
# Set X-Frame-Options to deny to prevent clickjacking
|
||||
add_header X-Frame-Options DENY always;
|
||||
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
|
||||
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
||||
add_header Cross-Origin-Opener-Policy same-origin always;
|
||||
@@ -0,0 +1,4 @@
|
||||
include /etc/nginx/zulip-include/headers;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Timing-Allow-Origin *;
|
||||
11
puppet/zulip/files/nginx/zulip-include-common/proxy
Normal file
11
puppet/zulip/files/nginx/zulip-include-common/proxy
Normal file
@@ -0,0 +1,11 @@
|
||||
proxy_http_version 1.1;
|
||||
# Clearing the Connection header is required for keepalives from the load balancer
|
||||
# http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $trusted_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-Ip $trusted_remote_addr;
|
||||
proxy_set_header X-Proxy-Misconfiguration $x_proxy_misconfiguration;
|
||||
proxy_next_upstream off;
|
||||
proxy_redirect off;
|
||||
@@ -0,0 +1,3 @@
|
||||
include /etc/nginx/zulip-include/proxy;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 1200;
|
||||
@@ -0,0 +1,4 @@
|
||||
include /etc/nginx/zulip-include/headers;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Headers Authorization always;
|
||||
add_header Access-Control-Allow-Methods 'OPTIONS, GET, DELETE' always;
|
||||
165
puppet/zulip/files/nginx/zulip-include-frontend/app
Normal file
165
puppet/zulip/files/nginx/zulip-include-frontend/app
Normal file
@@ -0,0 +1,165 @@
|
||||
access_log /var/log/nginx/access.log combined_with_host_and_time;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
include /etc/nginx/zulip-include/headers;
|
||||
|
||||
# Serve a custom error page when the app is down
|
||||
error_page 502 503 504 /static/webpack-bundles/5xx.html;
|
||||
|
||||
# Serve static files directly
|
||||
location /local-static {
|
||||
alias /home/zulip/local-static;
|
||||
}
|
||||
location /static/ {
|
||||
alias /home/zulip/prod-static/;
|
||||
gzip_static on;
|
||||
include /etc/nginx/zulip-include/headers;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Timing-Allow-Origin *;
|
||||
|
||||
# Set a nonexistent path, so we just serve the nice Django 404 page.
|
||||
error_page 404 /django_static_404.html;
|
||||
|
||||
# These files are hashed and thus immutable; cache them aggressively.
|
||||
# Django adds 12 hex digits; Webpack adds 20.
|
||||
location ~ '\.[0-9a-f]{12}\.|[./][0-9a-f]{20}\.' {
|
||||
include /etc/nginx/zulip-include/immutable_headers;
|
||||
}
|
||||
}
|
||||
|
||||
# Canonical URL for the root is /help/.
|
||||
location = /help {
|
||||
return 301 /help/;
|
||||
}
|
||||
|
||||
# Redirect to links without the trailing slash except for root /help/.
|
||||
location ~ ^(/help(?:/[^/]+)+)/+$ {
|
||||
return 301 $1$is_args$args;
|
||||
}
|
||||
|
||||
# Serve help center generated by astro/starlight.
|
||||
location /help {
|
||||
alias $help_alias;
|
||||
|
||||
gzip_static on;
|
||||
include /etc/nginx/zulip-include/headers;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Timing-Allow-Origin *;
|
||||
|
||||
error_page 404 /404.html;
|
||||
index /index.html;
|
||||
try_files $uri $uri/index.html =404;
|
||||
|
||||
# These files are hashed and thus immutable; cache them aggressively.
|
||||
# https://github.com/Pagefind/pagefind/issues/747#issuecomment-2510564644
|
||||
# https://github.com/withastro/docs/blob/53603ad048e8aedbca1aed77bac8eb00dcada79d/src/content/docs/en/guides/integrations-guide/node.mdx?plain=1#L304
|
||||
location ~ ^/help/(pagefind/.*\.(?:pf_fragment|pf_index|pf_meta)|_astro/.*)$ {
|
||||
include /etc/nginx/zulip-include/immutable_headers;
|
||||
}
|
||||
}
|
||||
|
||||
# Send longpoll requests to Tornado
|
||||
location = /json/events {
|
||||
if ($request_method = 'OPTIONS') {
|
||||
# add_header does not propagate into/out of blocks, so this
|
||||
# include cannot be factored out
|
||||
include /etc/nginx/zulip-include/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 /etc/nginx/zulip-include/headers;
|
||||
add_header Allow 'OPTIONS, GET, DELETE' always;
|
||||
return 405;
|
||||
}
|
||||
|
||||
proxy_pass $tornado_server;
|
||||
include /etc/nginx/zulip-include/proxy_longpolling;
|
||||
}
|
||||
|
||||
# Send longpoll requests to Tornado
|
||||
location = /api/v1/events {
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
include /etc/nginx/zulip-include/tornado_cors_headers;
|
||||
add_header Allow 'OPTIONS, GET, DELETE' always;
|
||||
return 204;
|
||||
}
|
||||
|
||||
if ($request_method !~ ^(GET|DELETE)$ ) {
|
||||
include /etc/nginx/zulip-include/headers;
|
||||
add_header Allow 'OPTIONS, GET, DELETE' always;
|
||||
return 405;
|
||||
}
|
||||
|
||||
include /etc/nginx/zulip-include/tornado_cors_headers;
|
||||
proxy_pass $tornado_server;
|
||||
include /etc/nginx/zulip-include/proxy_longpolling;
|
||||
}
|
||||
|
||||
# Handle X-Accel-Redirect from Tornado to Tornado
|
||||
location ~ ^/internal/tornado/(\d+)(/.*)$ {
|
||||
internal;
|
||||
proxy_pass http://tornado$1$2$is_args$args;
|
||||
include /etc/nginx/zulip-include/proxy_longpolling;
|
||||
}
|
||||
|
||||
location /api/v1/tus {
|
||||
include /etc/nginx/zulip-include/api_headers;
|
||||
include /etc/nginx/zulip-include/proxy;
|
||||
# https://github.com/tus/tusd/blob/main/examples/nginx.conf
|
||||
# Disable request body size limits, and stream the request and
|
||||
# response from tusd directly.
|
||||
client_max_body_size 0;
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
proxy_pass http://tusd;
|
||||
}
|
||||
|
||||
# Send everything else to Django via uWSGI
|
||||
location / {
|
||||
include uwsgi_params;
|
||||
}
|
||||
|
||||
# These Django routes not under /api are shared between mobile and
|
||||
# web, and thus need API headers added. We can't easily collapse
|
||||
# these blocks with the /api block, because regular expressions take
|
||||
# priority over paths in nginx's order-of-operations, and we don't
|
||||
# want to override the tornado configuration for /api/v1/events.
|
||||
location /thumbnail {
|
||||
include /etc/nginx/zulip-include/api_headers;
|
||||
|
||||
include uwsgi_params;
|
||||
}
|
||||
location /avatar {
|
||||
include /etc/nginx/zulip-include/api_headers;
|
||||
|
||||
include uwsgi_params;
|
||||
}
|
||||
location /user_uploads {
|
||||
include /etc/nginx/zulip-include/api_headers;
|
||||
|
||||
include uwsgi_params;
|
||||
}
|
||||
|
||||
location /api/internal/ {
|
||||
# These only need be accessed from localhost
|
||||
allow 127.0.0.1;
|
||||
allow ::1;
|
||||
deny all;
|
||||
|
||||
include /etc/nginx/zulip-include/api_headers;
|
||||
include uwsgi_params;
|
||||
}
|
||||
|
||||
# Send all API routes not covered above to Django via uWSGI
|
||||
location /api/ {
|
||||
include /etc/nginx/zulip-include/api_headers;
|
||||
|
||||
include uwsgi_params;
|
||||
}
|
||||
|
||||
include /etc/nginx/zulip-include/app.d/*.conf;
|
||||
@@ -0,0 +1,88 @@
|
||||
# Handle redirects to S3
|
||||
location ~ ^/internal/s3/(?<s3_hostname>[^/]+)/(?<s3_path>.*) {
|
||||
internal;
|
||||
include /etc/nginx/zulip-include/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.
|
||||
|
||||
# We slice the content into 5M chunks; this means that the client
|
||||
# doesn't need to wait for nginx to download and cache the full
|
||||
# content if the client just requested a small range (e.g. for
|
||||
# showing a thumbnail of a large video). 5M is chosen to be
|
||||
# enough for videos to be able to thumbnail in one slice, but not
|
||||
# take overly long to retrieve from S3, or cause overwhelming
|
||||
# numbers of cache entries for large files.
|
||||
slice 5m;
|
||||
proxy_set_header Range $slice_range;
|
||||
|
||||
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 206 1y;
|
||||
|
||||
# We only include the requested content-disposition (and range
|
||||
# slice) 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$slice_range;
|
||||
}
|
||||
|
||||
# Internal file-serving
|
||||
location /internal/local/uploads {
|
||||
internal;
|
||||
include /etc/nginx/zulip-include/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/zulip/uploads/files;
|
||||
}
|
||||
|
||||
location /internal/local/user_avatars {
|
||||
internal;
|
||||
include /etc/nginx/zulip-include/headers;
|
||||
add_header Content-Security-Policy "default-src 'none'; img-src 'self'";
|
||||
include /etc/nginx/zulip-include/uploads.types;
|
||||
alias /home/zulip/uploads/avatars;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
types {
|
||||
text/plain txt;
|
||||
|
||||
application/pdf pdf;
|
||||
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg jpe;
|
||||
image/png png;
|
||||
image/tiff tif tiff;
|
||||
image/webp webp;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
17
puppet/zulip/files/nginx/zulip-include-frontend/upstreams
Normal file
17
puppet/zulip/files/nginx/zulip-include-frontend/upstreams
Normal file
@@ -0,0 +1,17 @@
|
||||
include /etc/nginx/zulip-include/tornado-upstreams;
|
||||
|
||||
upstream django {
|
||||
server unix:/home/zulip/deployments/uwsgi-socket;
|
||||
}
|
||||
|
||||
upstream localhost_sso {
|
||||
server 127.0.0.1:8888;
|
||||
}
|
||||
|
||||
upstream camo {
|
||||
server 127.0.0.1:9292;
|
||||
}
|
||||
|
||||
upstream tusd {
|
||||
server 127.0.0.1:9900;
|
||||
}
|
||||
89
puppet/zulip/files/postgresql/centos_pg_hba.conf
Normal file
89
puppet/zulip/files/postgresql/centos_pg_hba.conf
Normal file
@@ -0,0 +1,89 @@
|
||||
# PostgreSQL Client Authentication Configuration File
|
||||
# ===================================================
|
||||
#
|
||||
# Refer to the "Client Authentication" section in the PostgreSQL
|
||||
# documentation for a complete description of this file. A short
|
||||
# synopsis follows.
|
||||
#
|
||||
# This file controls: which hosts are allowed to connect, how clients
|
||||
# are authenticated, which PostgreSQL user names they can use, which
|
||||
# databases they can access. Records take one of these forms:
|
||||
#
|
||||
# local DATABASE USER METHOD [OPTIONS]
|
||||
# host DATABASE USER ADDRESS METHOD [OPTIONS]
|
||||
# hostssl DATABASE USER ADDRESS METHOD [OPTIONS]
|
||||
# hostnossl DATABASE USER ADDRESS METHOD [OPTIONS]
|
||||
#
|
||||
# (The uppercase items must be replaced by actual values.)
|
||||
#
|
||||
# The first field is the connection type: "local" is a Unix-domain
|
||||
# socket, "host" is either a plain or SSL-encrypted TCP/IP socket,
|
||||
# "hostssl" is an SSL-encrypted TCP/IP socket, and "hostnossl" is a
|
||||
# plain TCP/IP socket.
|
||||
#
|
||||
# DATABASE can be "all", "sameuser", "samerole", "replication", a
|
||||
# database name, or a comma-separated list thereof. The "all"
|
||||
# keyword does not match "replication". Access to replication
|
||||
# must be enabled in a separate record (see example below).
|
||||
#
|
||||
# USER can be "all", a user name, a group name prefixed with "+", or a
|
||||
# comma-separated list thereof. In both the DATABASE and USER fields
|
||||
# you can also write a file name prefixed with "@" to include names
|
||||
# from a separate file.
|
||||
#
|
||||
# ADDRESS specifies the set of hosts the record matches. It can be a
|
||||
# host name, or it is made up of an IP address and a CIDR mask that is
|
||||
# an integer (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that
|
||||
# specifies the number of significant bits in the mask. A host name
|
||||
# that starts with a dot (.) matches a suffix of the actual host name.
|
||||
# Alternatively, you can write an IP address and netmask in separate
|
||||
# columns to specify the set of hosts. Instead of a CIDR-address, you
|
||||
# can write "samehost" to match any of the server's own IP addresses,
|
||||
# or "samenet" to match any address in any subnet that the server is
|
||||
# directly connected to.
|
||||
#
|
||||
# METHOD can be "trust", "reject", "md5", "password", "scram-sha-256",
|
||||
# "gss", "sspi", "ident", "peer", "pam", "ldap", "radius" or "cert".
|
||||
# Note that "password" sends passwords in clear text; "md5" or
|
||||
# "scram-sha-256" are preferred since they send encrypted passwords.
|
||||
#
|
||||
# OPTIONS are a set of options for the authentication in the format
|
||||
# NAME=VALUE. The available options depend on the different
|
||||
# authentication methods -- refer to the "Client Authentication"
|
||||
# section in the documentation for a list of which options are
|
||||
# available for which authentication methods.
|
||||
#
|
||||
# Database and user names containing spaces, commas, quotes and other
|
||||
# special characters must be quoted. Quoting one of the keywords
|
||||
# "all", "sameuser", "samerole" or "replication" makes the name lose
|
||||
# its special character, and just match a database or username with
|
||||
# that name.
|
||||
#
|
||||
# This file is read on server startup and when the server receives a
|
||||
# SIGHUP signal. If you edit the file on a running system, you have to
|
||||
# SIGHUP the server for the changes to take effect, run "pg_ctl reload",
|
||||
# or execute "SELECT pg_reload_conf()".
|
||||
#
|
||||
# Put your actual configuration here
|
||||
# ----------------------------------
|
||||
#
|
||||
# If you want to allow non-local connections, you need to add more
|
||||
# "host" records. In that case you will also need to make PostgreSQL
|
||||
# listen on a non-local interface via the listen_addresses
|
||||
# configuration parameter, or via the -i or -h command line switches.
|
||||
|
||||
|
||||
|
||||
# TYPE DATABASE USER ADDRESS METHOD
|
||||
|
||||
# "local" is for Unix domain socket connections only
|
||||
local all all peer
|
||||
# IPv4 local connections:
|
||||
host all all 127.0.0.1/32 md5
|
||||
# IPv6 local connections:
|
||||
host all all ::1/128 md5
|
||||
# Allow replication connections from localhost, by a user with the
|
||||
# replication privilege.
|
||||
local replication all peer
|
||||
host replication all 127.0.0.1/32 ident
|
||||
host replication all ::1/128 ident
|
||||
51
puppet/zulip/files/postgresql/env-wal-g
Executable file
51
puppet/zulip/files/postgresql/env-wal-g
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
if [ -z "$ZULIP_SECRETS_CONF" ]; then
|
||||
ZULIP_SECRETS_CONF=/etc/zulip/zulip-secrets.conf
|
||||
fi
|
||||
|
||||
export PGHOST=/var/run/postgresql/
|
||||
|
||||
WALG_DELTA_MAX_STEPS=$(crudini --get /etc/zulip/zulip.conf postgresql backups_incremental 2>/dev/null)
|
||||
export WALG_DELTA_MAX_STEPS
|
||||
|
||||
WALG_COMPRESSION_METHOD=$(crudini --get /etc/zulip/zulip.conf postgresql backups_compression_method 2>/dev/null)
|
||||
export WALG_COMPRESSION_METHOD
|
||||
|
||||
s3_backups_bucket=$(crudini --get "$ZULIP_SECRETS_CONF" secrets s3_backups_bucket 2>/dev/null)
|
||||
|
||||
if [ "$s3_backups_bucket" != "" ] || [ -n "$WALG_S3_PREFIX" ]; then
|
||||
AWS_REGION=$(crudini --get "$ZULIP_SECRETS_CONF" secrets s3_region 2>/dev/null)
|
||||
if [ "$AWS_REGION" = "" ]; then
|
||||
# Fall back to the current region, if possible
|
||||
AZ=$(ec2metadata --availability-zone || true)
|
||||
if [ -n "$AZ" ] && [ "$AZ" != "unavailable" ]; then
|
||||
AWS_REGION=$(echo "$AZ" | sed 's/.$//')
|
||||
fi
|
||||
fi
|
||||
export AWS_REGION
|
||||
AWS_ACCESS_KEY_ID=$(crudini --get "$ZULIP_SECRETS_CONF" secrets s3_backups_key 2>/dev/null)
|
||||
export AWS_ACCESS_KEY_ID
|
||||
AWS_SECRET_ACCESS_KEY=$(crudini --get "$ZULIP_SECRETS_CONF" secrets s3_backups_secret_key 2>/dev/null)
|
||||
export AWS_SECRET_ACCESS_KEY
|
||||
: "${WALG_S3_PREFIX:=s3://$s3_backups_bucket}"
|
||||
export WALG_S3_PREFIX
|
||||
|
||||
if storage_class=$(crudini --get /etc/zulip/zulip.conf postgresql backups_storage_class 2>/dev/null); then
|
||||
export WALG_S3_STORAGE_CLASS="$storage_class"
|
||||
fi
|
||||
else
|
||||
WALG_FILE_PREFIX=$(crudini --get /etc/zulip/zulip.conf postgresql backups_directory 2>/dev/null)
|
||||
if [ "$WALG_FILE_PREFIX" != "" ]; then
|
||||
export WALG_FILE_PREFIX
|
||||
if [ "$WALG_DELTA_MAX_STEPS" = "" ]; then
|
||||
# Default to only taking a full backup every week
|
||||
export WALG_DELTA_MAX_STEPS=6
|
||||
fi
|
||||
|
||||
else
|
||||
echo "Could not determine where to back up data to!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exec /usr/local/bin/wal-g "$@"
|
||||
81
puppet/zulip/files/postgresql/pg_backup_and_purge
Executable file
81
puppet/zulip/files/postgresql/pg_backup_and_purge
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
import configparser
|
||||
import glob
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
logging.Formatter.converter = time.gmtime
|
||||
logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
config_file = configparser.RawConfigParser()
|
||||
config_file.read("/etc/zulip/zulip.conf")
|
||||
|
||||
|
||||
def get_config(
|
||||
section: str,
|
||||
key: str,
|
||||
default_value: str = "",
|
||||
) -> str:
|
||||
if config_file.has_option(section, key):
|
||||
return config_file.get(section, key)
|
||||
return default_value
|
||||
|
||||
|
||||
replicas = subprocess.check_output(
|
||||
["psql", "-v", "ON_ERROR_STOP=1", "-t", "-c", "SELECT COUNT(*) FROM pg_stat_replication"],
|
||||
stdin=subprocess.DEVNULL,
|
||||
text=True,
|
||||
).strip()
|
||||
if int(replicas) > 0:
|
||||
# We are the primary and we have replicas; we expect that backups
|
||||
# will be taken on one of them.
|
||||
sys.exit(0)
|
||||
|
||||
skip_backups = get_config("postgresql", "skip_backups", "")
|
||||
if skip_backups.lower() in [1, "y", "t", "yes", "true", "enable", "enabled"]:
|
||||
sys.exit(0)
|
||||
|
||||
is_rhel_based = os.path.exists("/etc/redhat-release")
|
||||
if is_rhel_based:
|
||||
pg_data_paths = glob.glob("/var/lib/pgsql/*/data")
|
||||
else:
|
||||
pg_data_paths = glob.glob("/var/lib/postgresql/*/main")
|
||||
if len(pg_data_paths) != 1:
|
||||
print(f"PostgreSQL installation is not unique: {pg_data_paths}")
|
||||
sys.exit(1)
|
||||
pg_data_path = pg_data_paths[0]
|
||||
|
||||
disk_concurrency = get_config("postgresql", "backups_disk_concurrency", "1")
|
||||
env = os.environ.copy()
|
||||
env["WALG_UPLOAD_DISK_CONCURRENCY"] = disk_concurrency
|
||||
subprocess.check_call(["env-wal-g", "backup-push", pg_data_path], env=env)
|
||||
|
||||
data = json.loads(subprocess.check_output(["env-wal-g", "backup-list", "--json"]))
|
||||
# Use strptime for Python < 3.11 compatibility; we might not have
|
||||
# backports-datetime-fromisoformat here
|
||||
backups = {
|
||||
datetime.strptime(backup["time"], "%Y-%m-%dT%H:%M:%S%z"): backup["backup_name"]
|
||||
for backup in data
|
||||
}
|
||||
|
||||
one_month_ago = datetime.now(tz=timezone.utc) - timedelta(days=30)
|
||||
for date in sorted(backups.keys(), reverse=True):
|
||||
if date < one_month_ago:
|
||||
# We pass `FIND_FULL` such that if delta backups are being
|
||||
# used, we keep the prior FULL backup and all of the deltas
|
||||
# that we need. In practice, this means that if we're doing
|
||||
# weekly full backups (`backups_incremental = 6`), we only
|
||||
# delete any data once a week.
|
||||
subprocess.check_call(
|
||||
["env-wal-g", "delete", "--confirm", "before", "FIND_FULL", backups[date]]
|
||||
)
|
||||
# Because we're going from most recent to least recent, we
|
||||
# only have to do one delete operation
|
||||
break
|
||||
@@ -0,0 +1,8 @@
|
||||
# This file is managed by Zulip's Puppet configuration.
|
||||
#
|
||||
# Linux 6.14+ charges (per-user) the rings from io_uring against to
|
||||
# RLIMIT_MEMLOCK, which is by default limited to 8MiB; the default
|
||||
# Zulip PostgreSQL configuration needs ~13MiB. Set a limit sizably
|
||||
# above that.
|
||||
[Service]
|
||||
LimitMEMLOCK=256M
|
||||
276
puppet/zulip/files/postgresql/process_fts_updates
Executable file
276
puppet/zulip/files/postgresql/process_fts_updates
Executable file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env python3
|
||||
# Processes updates to PostgreSQL full-text search for new/edited messages.
|
||||
#
|
||||
# Zulip manages its PostgreSQL full-text search as follows. When the
|
||||
# content of a message is modified, a PostgreSQL trigger logs the
|
||||
# message ID to the `fts_update_log` table. In the background, this
|
||||
# program processes `fts_update_log`, updating the PostgreSQL full-text
|
||||
# search column search_tsvector in the main zerver_message.
|
||||
#
|
||||
# There are four cases this has to cover:
|
||||
#
|
||||
# 1. Running in development, with a venv but no
|
||||
# /home/zulip/deployments/current nor /etc/zulip/zulip.conf
|
||||
#
|
||||
# 2. Running in production, with postgresql on the same machine as the
|
||||
# frontend, with a venv and both /home/zulip/deployments/current
|
||||
# and /etc/zulip/zulip.conf
|
||||
#
|
||||
# 3. Running in production, on a postgresql machine which is not the
|
||||
# frontend, with a /etc/zulip/zulip.conf but no venv nor
|
||||
# /home/zulip/deployments/current
|
||||
#
|
||||
# 4. Running in production, on an application frontend server connected
|
||||
# to a remote postgresql server, because we cannot run code _on_ the
|
||||
# PostgreSQL server, such as in docker-zulip.
|
||||
#
|
||||
# Because of case (3), we cannot rely on functions from outside this
|
||||
# file (e.g. provided by scripts.lib.zulip_tools). For case (1),
|
||||
# however, we wish to import `zerver.settings` since we have no
|
||||
# /etc/zulip/zulip.conf to read, and that case _requires_ loading the
|
||||
# venv.
|
||||
#
|
||||
# We also must handle the cases where we are run as the `nagios` user,
|
||||
# which may not have permission to read all configuration files, and
|
||||
# thus (2) will look like (3).
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import logging
|
||||
import os
|
||||
import select
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Sequence
|
||||
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
from psycopg2.sql import SQL
|
||||
|
||||
BATCH_SIZE = 1000
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--quiet", action="store_true")
|
||||
parser.add_argument("--nagios-check", action="store_true")
|
||||
parser.add_argument("--nagios-user")
|
||||
options = parser.parse_args()
|
||||
|
||||
logging.Formatter.converter = time.gmtime
|
||||
logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s")
|
||||
logger = logging.getLogger("process_fts_updates")
|
||||
if options.quiet:
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def update_fts_columns(conn: psycopg2.extensions.connection) -> int:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"SELECT id, message_id FROM fts_update_log ORDER BY id LIMIT %s FOR UPDATE SKIP LOCKED",
|
||||
(BATCH_SIZE,),
|
||||
)
|
||||
cursor.arraysize = BATCH_SIZE
|
||||
parts = list(zip(*cursor.fetchmany(), strict=True))
|
||||
if not parts:
|
||||
row_ids: Sequence[int] = []
|
||||
message_ids: Sequence[int] = []
|
||||
else:
|
||||
row_ids, message_ids = parts[0], parts[1]
|
||||
|
||||
if message_ids:
|
||||
if USING_PGROONGA:
|
||||
update_sql = SQL(
|
||||
"search_pgroonga = escape_html(subject) || ' ' || rendered_content"
|
||||
)
|
||||
else:
|
||||
update_sql = SQL(
|
||||
"search_tsvector = to_tsvector('zulip.english_us_search', subject || rendered_content)"
|
||||
)
|
||||
cursor.execute(
|
||||
SQL(
|
||||
"UPDATE zerver_message SET {update_sql} "
|
||||
"WHERE ctid IN ("
|
||||
" SELECT ctid FROM zerver_message"
|
||||
" WHERE id IN %s"
|
||||
" ORDER BY id FOR UPDATE"
|
||||
")"
|
||||
).format(update_sql=update_sql),
|
||||
(message_ids,),
|
||||
)
|
||||
if row_ids:
|
||||
cursor.execute("DELETE FROM fts_update_log WHERE id IN %s", (row_ids,))
|
||||
conn.commit()
|
||||
return len(row_ids)
|
||||
|
||||
|
||||
def update_all_rows(msg: str, conn: psycopg2.extensions.connection) -> None:
|
||||
while True:
|
||||
start_time = time.perf_counter()
|
||||
rows_updated = update_fts_columns(conn)
|
||||
if rows_updated:
|
||||
logger.log(
|
||||
logging.INFO,
|
||||
"process_fts_updates: %s %d rows, %d rows/sec",
|
||||
msg,
|
||||
rows_updated,
|
||||
rows_updated / (time.perf_counter() - start_time),
|
||||
)
|
||||
|
||||
if rows_updated != BATCH_SIZE:
|
||||
return
|
||||
|
||||
|
||||
def am_master(conn: psycopg2.extensions.connection) -> bool:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("SELECT pg_is_in_recovery()")
|
||||
return not cursor.fetchall()[0][0]
|
||||
|
||||
|
||||
def get_config(
|
||||
config_file: configparser.RawConfigParser,
|
||||
section: str,
|
||||
key: str,
|
||||
default_value: str = "",
|
||||
) -> str:
|
||||
if config_file.has_option(section, key):
|
||||
return config_file.get(section, key)
|
||||
return default_value
|
||||
|
||||
|
||||
pg_args = {}
|
||||
|
||||
USING_PGROONGA = False
|
||||
try:
|
||||
# Case (1); we insert the path to the development root.
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..")))
|
||||
|
||||
# Cases (2) and (4); we insert the path to the production root.
|
||||
# This likely works out the same as the above path.
|
||||
#
|
||||
# We insert this path after the above, so that if running this
|
||||
# command from a specific non-current Zulip deployment, we prefer
|
||||
# that deployment's libraries.
|
||||
sys.path.insert(1, "/home/zulip/deployments/current")
|
||||
|
||||
# For cases (2) and (4), we also need to set up the virtualenv, so we
|
||||
# can read the Django settings.
|
||||
from scripts.lib.setup_path import setup_path
|
||||
|
||||
setup_path()
|
||||
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.settings"
|
||||
from django.conf import settings
|
||||
|
||||
pg_args["host"] = settings.DATABASES["default"]["HOST"]
|
||||
pg_args["port"] = settings.DATABASES["default"].get("PORT")
|
||||
pg_args["password"] = settings.DATABASES["default"].get("PASSWORD")
|
||||
pg_args["user"] = settings.DATABASES["default"]["USER"]
|
||||
pg_args["dbname"] = settings.DATABASES["default"]["NAME"]
|
||||
pg_args["sslmode"] = settings.DATABASES["default"]["OPTIONS"].get("sslmode")
|
||||
pg_args["connect_timeout"] = "600"
|
||||
USING_PGROONGA = settings.USING_PGROONGA
|
||||
except (ImportError, PermissionError):
|
||||
# Case (3): we know that the PostgreSQL server is on this host.
|
||||
pg_args["user"] = "zulip"
|
||||
|
||||
config_file = configparser.RawConfigParser()
|
||||
config_file.read("/etc/zulip/zulip.conf")
|
||||
|
||||
if get_config(config_file, "machine", "pgroonga", "false").lower() in [
|
||||
"1",
|
||||
"y",
|
||||
"t",
|
||||
"yes",
|
||||
"true",
|
||||
"enable",
|
||||
"enabled",
|
||||
]:
|
||||
USING_PGROONGA = True
|
||||
|
||||
pg_args["user"] = get_config(config_file, "postgresql", "database_user", "zulip")
|
||||
if pg_args["user"] != "zulip":
|
||||
secrets_file = configparser.RawConfigParser()
|
||||
secrets_file.read("/etc/zulip/zulip-secrets.conf")
|
||||
pg_args["password"] = get_config(secrets_file, "secrets", "postgres_password")
|
||||
pg_args["host"] = "localhost"
|
||||
pg_args["dbname"] = get_config(config_file, "postgresql", "database_name", "zulip")
|
||||
|
||||
conn: psycopg2.extensions.connection | None
|
||||
|
||||
if options.nagios_check:
|
||||
# Nagios connects as itself, unless you specify otherwise
|
||||
if options.nagios_user:
|
||||
pg_args["user"] = options.nagios_user
|
||||
else:
|
||||
del pg_args["user"]
|
||||
# connection_factory=None lets mypy understand the return type
|
||||
conn = psycopg2.connect(connection_factory=None, **pg_args)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT count(*) FROM fts_update_log")
|
||||
num = cursor.fetchall()[0][0]
|
||||
|
||||
# nagios exit codes
|
||||
states = {
|
||||
"OK": 0,
|
||||
"WARNING": 1,
|
||||
"CRITICAL": 2,
|
||||
"UNKNOWN": 3,
|
||||
}
|
||||
|
||||
state = "OK"
|
||||
if num > 5:
|
||||
state = "CRITICAL"
|
||||
print(f"{state}: {num} rows in fts_update_log table")
|
||||
sys.exit(states[state])
|
||||
|
||||
|
||||
conn = None
|
||||
|
||||
retries = 1
|
||||
|
||||
while True:
|
||||
try:
|
||||
if conn is None:
|
||||
# connection_factory=None lets mypy understand the return type
|
||||
conn = psycopg2.connect(connection_factory=None, **pg_args)
|
||||
retries = 30
|
||||
|
||||
conn.autocommit = False
|
||||
conn.isolation_level = psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED
|
||||
|
||||
first_check = True
|
||||
while not am_master(conn):
|
||||
if first_check:
|
||||
first_check = False
|
||||
logger.warning("In recovery; sleeping")
|
||||
time.sleep(5)
|
||||
|
||||
logger.debug("process_fts_updates: listening for search index updates")
|
||||
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("LISTEN fts_update_log;")
|
||||
conn.commit()
|
||||
|
||||
# Catch up on any historical columns
|
||||
update_all_rows("Caught up", conn)
|
||||
|
||||
if select.select([conn], [], [], 30) != ([], [], []):
|
||||
conn.poll()
|
||||
conn.notifies.clear()
|
||||
update_all_rows("Updated", conn)
|
||||
|
||||
except psycopg2.OperationalError as e:
|
||||
# We will end up here if the database is restarted, or becomes read-only.
|
||||
retries -= 1
|
||||
if retries <= 0:
|
||||
raise
|
||||
logger.info(e.pgerror, exc_info=True)
|
||||
logger.info("Sleeping and reconnecting")
|
||||
time.sleep(5)
|
||||
if conn is not None:
|
||||
conn.close()
|
||||
conn = None
|
||||
except KeyboardInterrupt:
|
||||
print(sys.argv[0], "exited after receiving KeyboardInterrupt")
|
||||
break
|
||||
162
puppet/zulip/files/postgresql/wal-g-exporter
Executable file
162
puppet/zulip/files/postgresql/wal-g-exporter
Executable file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
import configparser
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from collections.abc import Mapping
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from typing import Protocol
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
|
||||
|
||||
class GaugeMetric(Protocol):
|
||||
def __call__(self, value: float, labels: Mapping[str, str] | None = None, /) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class WalGPrometheusServer(BaseHTTPRequestHandler):
|
||||
METRIC_PREFIX = "wal_g_backup_"
|
||||
|
||||
metrics: dict[str, list[str]] = {}
|
||||
metric_values: dict[str, dict[str, str]] = defaultdict(dict)
|
||||
|
||||
server_version = "wal-g-prometheus-server/1.0"
|
||||
|
||||
def gauge(
|
||||
self, name: str, description: str | None = None, default_value: float | None = None
|
||||
) -> GaugeMetric:
|
||||
if name in self.metrics:
|
||||
raise ValueError(f"Redefinition of {name} metric")
|
||||
self.metrics[name] = [f"# TYPE {self.METRIC_PREFIX}{name} gauge"]
|
||||
if description is not None:
|
||||
self.metrics[name].append(f"# HELP {self.METRIC_PREFIX}{name} {description}")
|
||||
|
||||
def inner(value: float, labels: Mapping[str, str] | None = None) -> None:
|
||||
label_str = ""
|
||||
if labels:
|
||||
label_str = "{" + ",".join(f'{k}="{v}"' for k, v in labels.items()) + "}"
|
||||
self.metric_values[name][label_str] = f"{self.METRIC_PREFIX}{name}{label_str} {value}"
|
||||
|
||||
if default_value is not None:
|
||||
inner(default_value)
|
||||
return inner
|
||||
|
||||
def print_metrics(self) -> None:
|
||||
lines = []
|
||||
for metric_name in self.metrics:
|
||||
if self.metric_values[metric_name]:
|
||||
# Print preamble
|
||||
lines += self.metrics[metric_name]
|
||||
lines += self.metric_values[metric_name].values()
|
||||
lines.append("")
|
||||
self.wfile.write("\n".join(lines).encode())
|
||||
|
||||
def do_GET(self) -> None:
|
||||
if urlsplit(self.path).path != "/metrics":
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
sys.stderr.flush()
|
||||
return
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/plain; version=0.0.4")
|
||||
self.end_headers()
|
||||
|
||||
self.metrics = {}
|
||||
self.metric_values = defaultdict(dict)
|
||||
|
||||
backup_ok = self.gauge("ok", "If wal-g backup-list was OK", 0)
|
||||
backup_count = self.gauge("count", "Number of backups")
|
||||
backup_earliest_age_seconds = self.gauge("earliest_age_seconds", "Age of the oldest backup")
|
||||
backup_latest_age_seconds = self.gauge(
|
||||
"latest_age_seconds", "Age of the most recent backup"
|
||||
)
|
||||
backup_latest_duration_seconds = self.gauge(
|
||||
"latest_duration_seconds", "Duration the most recent backup took, in seconds"
|
||||
)
|
||||
backup_latest_compressed_size_bytes = self.gauge(
|
||||
"latest_compressed_size_bytes", "Size of the most recent backup, in bytes"
|
||||
)
|
||||
backup_latest_uncompressed_size_bytes = self.gauge(
|
||||
"latest_uncompressed_size_bytes",
|
||||
"Uncompressed size of the most recent backup, in bytes",
|
||||
)
|
||||
backup_total_compressed_size_bytes = self.gauge(
|
||||
"total_compressed_size_bytes", "Total compressed size of all backups, in bytes"
|
||||
)
|
||||
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
try:
|
||||
query = parse_qs(urlsplit(self.path).query)
|
||||
modified_env = os.environ.copy()
|
||||
if query.get("bucket") and len(query["bucket"]) == 1:
|
||||
bucket = query["bucket"][0]
|
||||
modified_env["WALG_S3_PREFIX"] = f"s3://{bucket}"
|
||||
else:
|
||||
config_file = configparser.RawConfigParser()
|
||||
config_file.read("/etc/zulip/zulip-secrets.conf")
|
||||
bucket = config_file["secrets"]["s3_backups_bucket"]
|
||||
|
||||
backup_list_output = subprocess.check_output(
|
||||
["env-wal-g", "backup-list", "--detail", "--json"],
|
||||
text=True,
|
||||
env=modified_env,
|
||||
)
|
||||
data = json.loads(backup_list_output) if backup_list_output else []
|
||||
backup_count(len(data), {"bucket": bucket})
|
||||
|
||||
backup_total_compressed_size_bytes(
|
||||
sum(e["compressed_size"] for e in data), {"bucket": bucket}
|
||||
)
|
||||
|
||||
if len(data) > 0:
|
||||
data.sort(key=lambda e: e["start_time"], reverse=True)
|
||||
latest = data[0]
|
||||
labels = {
|
||||
"host": latest["hostname"],
|
||||
"pg_version": str(latest["pg_version"]),
|
||||
"bucket": bucket,
|
||||
}
|
||||
backup_latest_compressed_size_bytes(latest["compressed_size"], labels)
|
||||
backup_latest_uncompressed_size_bytes(latest["uncompressed_size"], labels)
|
||||
|
||||
def t(key: str, e: dict[str, str] = latest) -> datetime:
|
||||
return datetime.strptime(e[key], e["date_fmt"]).replace(tzinfo=timezone.utc)
|
||||
|
||||
backup_earliest_age_seconds(
|
||||
(now - t("start_time", data[-1])) / timedelta(seconds=1),
|
||||
{
|
||||
"host": data[-1]["hostname"],
|
||||
"pg_version": data[-1]["pg_version"],
|
||||
"bucket": bucket,
|
||||
},
|
||||
)
|
||||
backup_latest_age_seconds((now - t("start_time")) / timedelta(seconds=1), labels)
|
||||
backup_latest_duration_seconds(
|
||||
(t("finish_time") - t("start_time")) / timedelta(seconds=1), labels
|
||||
)
|
||||
backup_ok(1)
|
||||
except Exception:
|
||||
logging.exception("Error while getting backup information")
|
||||
finally:
|
||||
self.print_metrics()
|
||||
self.log_message(
|
||||
"Served in %.2f seconds",
|
||||
(datetime.now(tz=timezone.utc) - now) / timedelta(seconds=1),
|
||||
)
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")
|
||||
server = HTTPServer(("127.0.0.1", 9188), WalGPrometheusServer)
|
||||
logging.info("Starting server...")
|
||||
with contextlib.suppress(KeyboardInterrupt):
|
||||
server.serve_forever()
|
||||
|
||||
server.server_close()
|
||||
logging.info("Stopping server...")
|
||||
149
puppet/zulip/files/postgresql/zulip_english.stop
Normal file
149
puppet/zulip/files/postgresql/zulip_english.stop
Normal file
@@ -0,0 +1,149 @@
|
||||
i
|
||||
me
|
||||
my
|
||||
myself
|
||||
we
|
||||
our
|
||||
ours
|
||||
ourselves
|
||||
you
|
||||
your
|
||||
yours
|
||||
yourself
|
||||
yourselves
|
||||
he
|
||||
him
|
||||
his
|
||||
himself
|
||||
she
|
||||
her
|
||||
hers
|
||||
herself
|
||||
it
|
||||
its
|
||||
itself
|
||||
they
|
||||
them
|
||||
their
|
||||
theirs
|
||||
themselves
|
||||
what
|
||||
which
|
||||
who
|
||||
whom
|
||||
this
|
||||
that
|
||||
these
|
||||
those
|
||||
am
|
||||
is
|
||||
are
|
||||
was
|
||||
were
|
||||
be
|
||||
been
|
||||
being
|
||||
have
|
||||
has
|
||||
had
|
||||
having
|
||||
do
|
||||
does
|
||||
did
|
||||
doing
|
||||
a
|
||||
an
|
||||
the
|
||||
and
|
||||
but
|
||||
if
|
||||
or
|
||||
because
|
||||
as
|
||||
until
|
||||
while
|
||||
of
|
||||
at
|
||||
by
|
||||
for
|
||||
with
|
||||
about
|
||||
against
|
||||
between
|
||||
into
|
||||
through
|
||||
during
|
||||
before
|
||||
after
|
||||
above
|
||||
below
|
||||
to
|
||||
from
|
||||
up
|
||||
down
|
||||
in
|
||||
out
|
||||
on
|
||||
off
|
||||
over
|
||||
under
|
||||
again
|
||||
further
|
||||
then
|
||||
once
|
||||
here
|
||||
there
|
||||
when
|
||||
where
|
||||
why
|
||||
how
|
||||
all
|
||||
any
|
||||
both
|
||||
each
|
||||
few
|
||||
more
|
||||
most
|
||||
other
|
||||
some
|
||||
such
|
||||
no
|
||||
nor
|
||||
not
|
||||
only
|
||||
own
|
||||
same
|
||||
so
|
||||
than
|
||||
too
|
||||
very
|
||||
s
|
||||
t
|
||||
can
|
||||
will
|
||||
just
|
||||
don
|
||||
should
|
||||
now
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
g
|
||||
h
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
u
|
||||
v
|
||||
w
|
||||
x
|
||||
y
|
||||
z
|
||||
15
puppet/zulip/files/rabbitmq/rabbitmq-env.conf
Normal file
15
puppet/zulip/files/rabbitmq/rabbitmq-env.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
# Defaults to rabbit. This can be useful if you want to run more than one node
|
||||
# per machine - RABBITMQ_NODENAME should be unique per erlang-node-and-machine
|
||||
# combination. See the clustering on a single machine guide for details:
|
||||
# http://www.rabbitmq.com/clustering.html#single-machine
|
||||
#
|
||||
# By default, we set nodename to zulip@localhost so it will always resolve
|
||||
NODENAME=zulip@localhost
|
||||
|
||||
# By default RabbitMQ will bind to all interfaces, on IPv4 and IPv6 if
|
||||
# available. Set this if you only want to bind to one network interface or#
|
||||
# address family.
|
||||
#NODE_IP_ADDRESS=127.0.0.1
|
||||
|
||||
# Defaults to 5672.
|
||||
#NODE_PORT=5672
|
||||
9
puppet/zulip/files/rabbitmq/rabbitmq.config
Normal file
9
puppet/zulip/files/rabbitmq/rabbitmq.config
Normal file
@@ -0,0 +1,9 @@
|
||||
[{kernel, [{inet_dist_use_interface, {127,0,0,1}}]},
|
||||
{rabbit, [{tcp_listeners, [{"127.0.0.1", 5672}]},
|
||||
%% We disable consumer_timeout. Our queue workers ACK an
|
||||
%% event only after consume() finishes; consumer_timeout doesn't
|
||||
%% abort a slow job, it just requeues it for a redundant,
|
||||
%% duplicate run. This mechanism doesn't provide anything helpful
|
||||
%% for us.
|
||||
{consumer_timeout, undefined}]},
|
||||
{rabbitmq_prometheus, [{tcp_config, [{ip, {127,0,0,1}}, {port, 15692}]}]}].
|
||||
2
puppet/zulip/files/sasl2/memcached.conf
Normal file
2
puppet/zulip/files/sasl2/memcached.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
mech_list: plain
|
||||
sasldb_path: /etc/sasl2/memcached-sasldb2
|
||||
25
puppet/zulip/files/secret-env-wrapper
Executable file
25
puppet/zulip/files/secret-env-wrapper
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" == "--" ]; then
|
||||
shift
|
||||
exec "$@"
|
||||
elif [[ "$arg" == *"="* ]]; then
|
||||
shift
|
||||
varname="${arg%%=*}"
|
||||
secretname="${arg#*=}"
|
||||
secret=$(crudini --get /etc/zulip/zulip-secrets.conf secrets "$secretname")
|
||||
export "$varname"="$secret"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
||||
done
|
||||
|
||||
{
|
||||
echo "Usage:"
|
||||
echo " secret-env-wrapper ENVNAME=secretname binary [argument [argument [...]]]"
|
||||
} >&2
|
||||
|
||||
exit 1
|
||||
6
puppet/zulip/files/supervisor/conf.d/cron.conf
Normal file
6
puppet/zulip/files/supervisor/conf.d/cron.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
; Used in docker.pp, starts cron (for docker use only)
|
||||
[program:cron]
|
||||
command = /usr/sbin/cron -f -L 15
|
||||
stdout_events_enabled=true
|
||||
stderr_events_enabled=true
|
||||
autorestart = true
|
||||
6
puppet/zulip/files/supervisor/conf.d/nginx.conf
Normal file
6
puppet/zulip/files/supervisor/conf.d/nginx.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
; Used in docker.pp, starts nginx web server (for docker use only)
|
||||
[program:nginx]
|
||||
command = /usr/sbin/nginx -g 'daemon off;'
|
||||
stdout_events_enabled=true
|
||||
stderr_events_enabled=true
|
||||
autorestart = true
|
||||
10
puppet/zulip/files/supervisor/conf.d/zulip_db.conf
Normal file
10
puppet/zulip/files/supervisor/conf.d/zulip_db.conf
Normal file
@@ -0,0 +1,10 @@
|
||||
[program:process-fts-updates]
|
||||
command=/usr/local/bin/process_fts_updates
|
||||
priority=600 ; the relative start priority (default 999)
|
||||
autostart=true ; start at supervisord start (default: true)
|
||||
autorestart=true ; whether/when to restart (default: unexpected)
|
||||
stopsignal=TERM ; signal used to kill process (default TERM)
|
||||
stopwaitsecs=30 ; max num secs to wait b4 SIGKILL (default 10)
|
||||
user=zulip ; setuid to this UNIX account to run the program
|
||||
redirect_stderr=true ; redirect proc stderr to stdout (default false)
|
||||
stdout_logfile=/var/log/zulip/fts-updates.log ; stdout log path, NONE for none; default AUTO
|
||||
2
puppet/zulip/files/systemd/system.conf.d/limits.conf
Normal file
2
puppet/zulip/files/systemd/system.conf.d/limits.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
[Manager]
|
||||
DefaultLimitNOFILE=1000000
|
||||
Reference in New Issue
Block a user