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
|
||||
25
puppet/zulip/lib/facter/container_memory_limit.rb
Normal file
25
puppet/zulip/lib/facter/container_memory_limit.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
Facter.add(:container_memory_limit_mb) do
|
||||
confine kernel: 'Linux'
|
||||
|
||||
setcode do
|
||||
begin
|
||||
memory_limit_mb = nil
|
||||
|
||||
# Check for cgroup v2
|
||||
if File.exist?('/sys/fs/cgroup/memory.max')
|
||||
val = File.read('/sys/fs/cgroup/memory.max').strip
|
||||
memory_limit_mb = val.to_i / 1024 / 1024 unless val == 'max'
|
||||
|
||||
# Fallback to cgroup v1
|
||||
elsif File.exist?('/sys/fs/cgroup/memory/memory.limit_in_bytes')
|
||||
val = File.read('/sys/fs/cgroup/memory/memory.limit_in_bytes').strip.to_i
|
||||
memory_limit_mb = val / 1024 / 1024 if val < 9223372036854771712
|
||||
end
|
||||
|
||||
memory_limit_mb
|
||||
rescue => e
|
||||
Facter.debug("container_memory_limit_mb error: #{e}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
16
puppet/zulip/lib/facter/io_uring_available.rb
Normal file
16
puppet/zulip/lib/facter/io_uring_available.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
Facter.add(:io_uring_available) do
|
||||
confine :kernel => 'Linux'
|
||||
setcode do
|
||||
# Syscall 425 is io_uring_setup
|
||||
# https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/#x86_64_425
|
||||
# https://manpages.debian.org/trixie/liburing-dev/io_uring_setup.2.en.html
|
||||
#
|
||||
# We get ENOSYS if the kernel doesn't have the syscall, or EPERM
|
||||
# if it's disabled or restricted via seccomp (i.e. in Docker)
|
||||
result = Facter::Core::Execution.execute(
|
||||
"perl -MErrno=EPERM,ENOSYS -e 'syscall(425); exit((\$! != EPERM and \$! != ENOSYS) ? 0 : 1)'",
|
||||
:on_fail => :failed
|
||||
)
|
||||
result != :failed && $?.success?
|
||||
end
|
||||
end
|
||||
17
puppet/zulip/lib/facter/zulip_version.rb
Normal file
17
puppet/zulip/lib/facter/zulip_version.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
require "open3"
|
||||
|
||||
Facter.add(:zulip_version) do
|
||||
setcode do
|
||||
Dir.chdir(File.expand_path("../../../../", __dir__)) do
|
||||
output, stderr, status = Open3.capture3(
|
||||
"python3", "-c", "import version; print(version.ZULIP_VERSION_WITHOUT_COMMIT)"
|
||||
)
|
||||
if not status.success?
|
||||
Facter.debug("zulip_version error: #{stderr}")
|
||||
nil
|
||||
else
|
||||
output.strip
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
puppet/zulip/lib/puppet/functions/get_django_setting_slow.rb
Normal file
27
puppet/zulip/lib/puppet/functions/get_django_setting_slow.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
require "open3"
|
||||
|
||||
# Note that this is very slow (~350ms) and may get values which will
|
||||
# rapidly go out of date, since settings are changed much more
|
||||
# frequently than deploys -- in addition to potentially just not
|
||||
# working if we're not on the application server. We should generally
|
||||
# avoid using this if at all possible.
|
||||
|
||||
Puppet::Functions.create_function(:get_django_setting_slow) do
|
||||
def get_django_setting_slow(name)
|
||||
return nil unless File.exist?("/etc/zulip/settings.py")
|
||||
if Dir.exist?("/home/zulip/deployments/current")
|
||||
deploy_dir = "current"
|
||||
elsif Dir.exist?("/home/zulip/deployments/next")
|
||||
# First puppet runs during install don't have a "current" yet, only a "next"
|
||||
deploy_dir = "next"
|
||||
else
|
||||
return nil
|
||||
end
|
||||
output, _stderr, status = Open3.capture3("/home/zulip/deployments/#{deploy_dir}/scripts/get-django-setting", name)
|
||||
if status.success?
|
||||
output.strip
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
16
puppet/zulip/lib/puppet/functions/resolver_ip.rb
Normal file
16
puppet/zulip/lib/puppet/functions/resolver_ip.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
require "resolv"
|
||||
|
||||
Puppet::Functions.create_function(:resolver_ip) do
|
||||
def resolver_ip()
|
||||
parsed = Resolv::DNS::Config.default_config_hash()
|
||||
if parsed[:nameserver].empty?
|
||||
raise 'No nameservers found in /etc/resolv.conf! Configure one by setting application_server.nameserver in /etc/zulip/zulip.conf'
|
||||
end
|
||||
resolver = parsed[:nameserver][0]
|
||||
if resolver.include?(':')
|
||||
'[' + resolver + ']'
|
||||
else
|
||||
resolver
|
||||
end
|
||||
end
|
||||
end
|
||||
19
puppet/zulip/lib/puppet/functions/zulipconf.rb
Normal file
19
puppet/zulip/lib/puppet/functions/zulipconf.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
require "open3"
|
||||
|
||||
Puppet::Functions.create_function(:zulipconf) do
|
||||
def zulipconf(section, key, default)
|
||||
zulip_conf_path = Facter.value("zulip_conf_path")
|
||||
output, _stderr, status = Open3.capture3("/usr/bin/crudini", "--get", "--", zulip_conf_path, section, key)
|
||||
if status.success?
|
||||
if [true, false].include? default
|
||||
# If the default is a bool, coerce into a bool. This list is also
|
||||
# maintained in scripts/lib/zulip_tools.py
|
||||
["1", "y", "t", "true", "yes", "enable", "enabled"].include? output.strip.downcase
|
||||
else
|
||||
output.strip
|
||||
end
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
end
|
||||
13
puppet/zulip/lib/puppet/functions/zulipconf_keys.rb
Normal file
13
puppet/zulip/lib/puppet/functions/zulipconf_keys.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
require "open3"
|
||||
|
||||
Puppet::Functions.create_function(:zulipconf_keys) do
|
||||
def zulipconf_keys(section)
|
||||
zulip_conf_path = Facter.value("zulip_conf_path")
|
||||
output, _stderr, status = Open3.capture3("/usr/bin/crudini", "--get", "--", zulip_conf_path, section)
|
||||
if status.success?
|
||||
return output.lines.map { |l| l.strip }
|
||||
else
|
||||
return []
|
||||
end
|
||||
end
|
||||
end
|
||||
23
puppet/zulip/lib/puppet/functions/zulipconf_nagios_hosts.rb
Normal file
23
puppet/zulip/lib/puppet/functions/zulipconf_nagios_hosts.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
require "open3"
|
||||
|
||||
Puppet::Functions.create_function(:zulipconf_nagios_hosts) do
|
||||
def zulipconf_nagios_hosts
|
||||
section = "nagios"
|
||||
prefix = "hosts_"
|
||||
zulip_conf_path = Facter.value("zulip_conf_path")
|
||||
keys, _stderr, status = Open3.capture3("/usr/bin/crudini", "--get", "--", zulip_conf_path, section)
|
||||
if status.success?
|
||||
filtered_keys = keys.lines.map { |l| l.strip }.select { |l| l.start_with?(prefix) }
|
||||
all_values = []
|
||||
filtered_keys.each do |key|
|
||||
values, _stderr, success = Open3.capture3("/usr/bin/crudini", "--get", "--", zulip_conf_path, section, key)
|
||||
if status.success?
|
||||
all_values += values.strip.split(/,\s*/)
|
||||
end
|
||||
end
|
||||
return all_values
|
||||
else
|
||||
return []
|
||||
end
|
||||
end
|
||||
end
|
||||
12
puppet/zulip/lib/puppet/functions/zulipsecret.rb
Normal file
12
puppet/zulip/lib/puppet/functions/zulipsecret.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
require "open3"
|
||||
|
||||
Puppet::Functions.create_function(:zulipsecret) do
|
||||
def zulipsecret(section, key, default)
|
||||
output, _stderr, status = Open3::capture3("/usr/bin/crudini", "--get", "--", "/etc/zulip/zulip-secrets.conf", section, key)
|
||||
if status.success?
|
||||
output.strip()
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
end
|
||||
21
puppet/zulip/lib/puppet/type/apache2conf.rb
Normal file
21
puppet/zulip/lib/puppet/type/apache2conf.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
Puppet::Type.newtype(:apache2conf) do
|
||||
ensurable
|
||||
newparam(:name) do
|
||||
desc "The name of the conf to enable"
|
||||
isnamevar
|
||||
end
|
||||
end
|
||||
|
||||
Puppet::Type.type(:apache2conf).provide(:apache2conf) do
|
||||
def exists?
|
||||
File.exists?("/etc/apache2/conf-enabled/" + resource[:name] + ".conf")
|
||||
end
|
||||
|
||||
def create
|
||||
system("a2enconf #{@resource[:name]}")
|
||||
end
|
||||
|
||||
def destroy
|
||||
system("a2disconf #{@resource[:name]}")
|
||||
end
|
||||
end
|
||||
21
puppet/zulip/lib/puppet/type/apache2mod.rb
Normal file
21
puppet/zulip/lib/puppet/type/apache2mod.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
Puppet::Type.newtype(:apache2mod) do
|
||||
ensurable
|
||||
newparam(:name) do
|
||||
desc "The name of the module to enable"
|
||||
isnamevar
|
||||
end
|
||||
end
|
||||
|
||||
Puppet::Type.type(:apache2mod).provide(:apache2mod) do
|
||||
def exists?
|
||||
File.exists?("/etc/apache2/mods-enabled/" + resource[:name] + ".load")
|
||||
end
|
||||
|
||||
def create
|
||||
system("a2enmod #{@resource[:name]}")
|
||||
end
|
||||
|
||||
def destroy
|
||||
system("a2dismod #{@resource[:name]}")
|
||||
end
|
||||
end
|
||||
21
puppet/zulip/lib/puppet/type/apache2site.rb
Normal file
21
puppet/zulip/lib/puppet/type/apache2site.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
Puppet::Type.newtype(:apache2site) do
|
||||
ensurable
|
||||
newparam(:name) do
|
||||
desc "The name of the site to enable"
|
||||
isnamevar
|
||||
end
|
||||
end
|
||||
|
||||
Puppet::Type.type(:apache2site).provide(:apache2site) do
|
||||
def exists?
|
||||
File.exists?("/etc/apache2/sites-enabled/" + resource[:name] + ".conf")
|
||||
end
|
||||
|
||||
def create
|
||||
system("a2ensite #{@resource[:name]}")
|
||||
end
|
||||
|
||||
def destroy
|
||||
system("a2ensite #{@resource[:name]}")
|
||||
end
|
||||
end
|
||||
43
puppet/zulip/manifests/apache_sso.pp
Normal file
43
puppet/zulip/manifests/apache_sso.pp
Normal file
@@ -0,0 +1,43 @@
|
||||
class zulip::apache_sso {
|
||||
include zulip::localhost_sso
|
||||
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {
|
||||
$apache_packages = [ 'apache2', 'libapache2-mod-wsgi-py3', ]
|
||||
$conf_dir = '/etc/apache2'
|
||||
$apache2 = 'apache2'
|
||||
}
|
||||
'RedHat': {
|
||||
$apache_packages = [ 'httpd', 'python36u-mod_wsgi', ]
|
||||
$conf_dir = '/etc/httpd'
|
||||
$apache2 = 'httpd'
|
||||
}
|
||||
default: {
|
||||
fail('osfamily not supported')
|
||||
}
|
||||
}
|
||||
package { $apache_packages: ensure => installed }
|
||||
|
||||
apache2mod { [ 'headers', 'proxy', 'proxy_http', 'rewrite', 'ssl', 'wsgi', ]:
|
||||
ensure => present,
|
||||
require => Package[$apache2],
|
||||
}
|
||||
|
||||
file { "${conf_dir}/ports.conf":
|
||||
ensure => file,
|
||||
require => Package[$apache2],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0640',
|
||||
source => 'puppet:///modules/zulip/apache/ports.conf',
|
||||
}
|
||||
|
||||
file { "${conf_dir}/sites-available/":
|
||||
recurse => true,
|
||||
require => Package[$apache2],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0640',
|
||||
source => 'puppet:///modules/zulip/apache/sites/',
|
||||
}
|
||||
}
|
||||
318
puppet/zulip/manifests/app_frontend_base.pp
Normal file
318
puppet/zulip/manifests/app_frontend_base.pp
Normal file
@@ -0,0 +1,318 @@
|
||||
# Minimal configuration to run a Zulip application server.
|
||||
# Default nginx configuration is included in extension app_frontend.pp.
|
||||
class zulip::app_frontend_base {
|
||||
include zulip::nginx
|
||||
include zulip::tusd
|
||||
include zulip::sasl_modules
|
||||
include zulip::supervisor
|
||||
include zulip::tornado_sharding
|
||||
include zulip::hooks::base
|
||||
|
||||
if $facts['os']['family'] == 'Debian' {
|
||||
# Upgrade and other tooling wants to be able to get a database
|
||||
# shell. This is not necessary on CentOS because the PostgreSQL
|
||||
# package already includes the client.
|
||||
include zulip::postgresql_client
|
||||
}
|
||||
zulip::safepackage {
|
||||
[
|
||||
# For `manage.py compilemessages` when upgrading from Git.
|
||||
'gettext',
|
||||
# For Slack import.
|
||||
'unzip',
|
||||
# Ensures `/etc/ldap/ldap.conf` exists; the default
|
||||
# `TLS_CACERTDIR` specified there is necessary for LDAP
|
||||
# authentication to work. This package is "Recommended" by
|
||||
# `libldap` where is required by postgres, so has been on most
|
||||
# Zulip servers by default; adding it here explicitly ensures it
|
||||
# is available on those that don't include the database server.
|
||||
'libldap-common',
|
||||
]:
|
||||
ensure => installed,
|
||||
}
|
||||
|
||||
file { '/etc/nginx/zulip-include/app':
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/nginx/zulip-include-frontend/app',
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
file { '/etc/nginx/zulip-include/uploads.types':
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/nginx/zulip-include-frontend/uploads.types',
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
file { '/etc/nginx/zulip-include/app.d/':
|
||||
ensure => directory,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
}
|
||||
file { '/etc/nginx/zulip-include/localhost.d/':
|
||||
ensure => directory,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
}
|
||||
|
||||
$loadbalancers = split(zulipconf('loadbalancer', 'ips', ''), ',')
|
||||
if $loadbalancers != [] {
|
||||
file { '/etc/nginx/zulip-include/app.d/keepalive-loadbalancer.conf':
|
||||
require => File['/etc/nginx/zulip-include/app.d'],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/nginx/zulip-include-app.d/keepalive-loadbalancer.conf',
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
} else {
|
||||
file { '/etc/nginx/zulip-include/app.d/keepalive-loadbalancer.conf':
|
||||
ensure => absent,
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
}
|
||||
file { '/etc/nginx/zulip-include/app.d/accept-loadbalancer.conf':
|
||||
# This moved to /etc/nginx/zulip-include/trusted-ip, via
|
||||
# nginx.pp. This block can be removed once direct Zulip upgrades
|
||||
# from Zulip 11 are no longer supported.
|
||||
ensure => absent,
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
file { '/etc/nginx/zulip-include/app.d/healthcheck.conf':
|
||||
require => File['/etc/nginx/zulip-include/app.d'],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/nginx/healthcheck.conf.template.erb'),
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
|
||||
file { '/etc/nginx/zulip-include/upstreams':
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/nginx/zulip-include-frontend/upstreams',
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
|
||||
$s3_memory_cache_size = zulipconf('application_server', 's3_memory_cache_size', '1M')
|
||||
$s3_disk_cache_size = zulipconf('application_server', 's3_disk_cache_size', '200M')
|
||||
$s3_cache_inactive_time = zulipconf('application_server', 's3_cache_inactive_time', '30d')
|
||||
$configured_nginx_resolver = zulipconf('application_server', 'nameserver', '')
|
||||
if $configured_nginx_resolver == '' {
|
||||
# This may fail in the unlikely change that there is no configured
|
||||
# resolver in /etc/resolv.conf, so only call it is unset in zulip.conf
|
||||
$nginx_resolver_ip = resolver_ip()
|
||||
} elsif (':' in $configured_nginx_resolver) and ! ('.' in $configured_nginx_resolver) and ! ('[' in $configured_nginx_resolver) {
|
||||
# Assume this is IPv6, which needs square brackets.
|
||||
$nginx_resolver_ip = "[${configured_nginx_resolver}]"
|
||||
} else {
|
||||
$nginx_resolver_ip = $configured_nginx_resolver
|
||||
}
|
||||
file { '/etc/nginx/zulip-include/s3-cache':
|
||||
require => [
|
||||
Package[$zulip::common::nginx],
|
||||
File['/srv/zulip-uploaded-files-cache'],
|
||||
],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/nginx/s3-cache.template.erb'),
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
|
||||
file { '/etc/nginx/zulip-include/app.d/uploads-internal.conf':
|
||||
ensure => file,
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
notify => Service['nginx'],
|
||||
source => 'puppet:///modules/zulip/nginx/zulip-include-frontend/uploads-internal.conf',
|
||||
}
|
||||
|
||||
# This determines whether we run queue processors multithreaded or
|
||||
# multiprocess. Multiprocess scales much better, but requires more
|
||||
# RAM; we just auto-detect based on available system RAM.
|
||||
#
|
||||
# Because Zulip can run in the multiprocess mode with 4GB of memory,
|
||||
# and it's a common instance size, we aim for that to be the cutoff
|
||||
# for this higher-performance mode.
|
||||
#
|
||||
# We use a cutoff less than 4000 here to detect systems advertised
|
||||
# as "4GB"; some may have as little as 4 x 1000^3 / 1024^2 ≈ 3815 MiB
|
||||
# of memory.
|
||||
$queues_multiprocess_default = $zulip::common::total_memory_mb > 3800
|
||||
$queues_multiprocess = zulipconf('application_server', 'queue_workers_multiprocess', $queues_multiprocess_default)
|
||||
$base_queues = [
|
||||
'deferred_work',
|
||||
'digest_emails',
|
||||
'email_mirror',
|
||||
'embed_links',
|
||||
'embedded_bots',
|
||||
'email_senders',
|
||||
'deferred_email_senders',
|
||||
'missedmessage_emails',
|
||||
'missedmessage_mobile_notifications',
|
||||
'outgoing_webhooks',
|
||||
'thumbnail',
|
||||
'user_activity',
|
||||
'user_activity_interval',
|
||||
]
|
||||
# Soft reactivations normally share the deferred_work queue; larger
|
||||
# servers can opt into a dedicated queue (and worker process) for them.
|
||||
$queues = zulipconf('application_server', 'dedicated_soft_reactivation_queue', false) ? {
|
||||
true => $base_queues + ['soft_reactivation'],
|
||||
default => $base_queues,
|
||||
}
|
||||
|
||||
if $zulip::common::total_memory_mb > 24000 {
|
||||
$uwsgi_default_processes = 16
|
||||
} elsif $zulip::common::total_memory_mb > 12000 {
|
||||
$uwsgi_default_processes = 8
|
||||
} elsif $zulip::common::total_memory_mb > 6000 {
|
||||
$uwsgi_default_processes = 6
|
||||
} elsif $zulip::common::total_memory_mb > 3000 {
|
||||
$uwsgi_default_processes = 4
|
||||
} else {
|
||||
$uwsgi_default_processes = 3
|
||||
}
|
||||
|
||||
# Not the different naming scheme for sharded workers, where each gets its own queue,
|
||||
# vs when multiple workers service the same queue.
|
||||
$worker_counts = Hash(zulipconf_keys('application_server').filter |$key| {
|
||||
$key =~ /_workers$/
|
||||
}.map |$key| {
|
||||
[regsubst($key, '_workers$', ''), Integer(zulipconf('application_server', $key, 1))]
|
||||
})
|
||||
$mobile_notification_shards = Integer(zulipconf('application_server', 'mobile_notification_shards', 1))
|
||||
$user_activity_shards = Integer(zulipconf('application_server', 'user_activity_shards', 1))
|
||||
$tornado_ports = $zulip::tornado_sharding::tornado_ports
|
||||
|
||||
$proxy_host = zulipconf('http_proxy', 'host', 'localhost')
|
||||
$proxy_port = zulipconf('http_proxy', 'port', '4750')
|
||||
|
||||
if ($proxy_host in ['localhost', '127.0.0.1', '::1']) and ($proxy_port == '4750') {
|
||||
include zulip::smokescreen
|
||||
}
|
||||
|
||||
$katex_server = zulipconf('application_server', 'katex_server', true)
|
||||
$katex_server_port = zulipconf('application_server', 'katex_server_port', '9700')
|
||||
|
||||
$tusd_server_listen = zulipconf('application_server', 'tusd_server_listen', '127.0.0.1')
|
||||
|
||||
# Supervisor sets this as HTTP_proxy/HTTPS_proxy in every process's
|
||||
# environment, so that all outgoing requests go through Smokescreen;
|
||||
# including those made directly by libraries rather than through
|
||||
# OutgoingSession.
|
||||
if $proxy_host != '' and $proxy_port != '' {
|
||||
$proxy = "http://${proxy_host}:${proxy_port}"
|
||||
} else {
|
||||
$proxy = ''
|
||||
}
|
||||
$custom_ca_path = zulipconf('application_server','custom_ca_path', '')
|
||||
if $custom_ca_path != '' {
|
||||
file { '/usr/local/share/ca-certificates/custom-zulip-ca.crt':
|
||||
ensure => file,
|
||||
source => $custom_ca_path,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
notify => Exec['update-ca-certificates'],
|
||||
}
|
||||
exec { 'update-ca-certificates':
|
||||
command => '/usr/sbin/update-ca-certificates',
|
||||
require => Package['ca-certificates'],
|
||||
before => File["${zulip::common::supervisor_conf_dir}/zulip.conf"],
|
||||
refreshonly => true,
|
||||
}
|
||||
$ca_bundle=',REQUESTS_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt"'
|
||||
} else {
|
||||
$ca_bundle=''
|
||||
}
|
||||
file { "${zulip::common::supervisor_conf_dir}/zulip.conf":
|
||||
ensure => file,
|
||||
require => [Package[supervisor], Exec['stage_updated_sharding']],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/supervisor/zulip.conf.template.erb'),
|
||||
notify => Service[$zulip::common::supervisor_service],
|
||||
}
|
||||
|
||||
$uwsgi_rolling_restart = zulipconf('application_server', 'rolling_restart', false)
|
||||
$uwsgi_listen_backlog_limit = zulipconf('application_server', 'uwsgi_listen_backlog_limit', 128)
|
||||
$uwsgi_processes = zulipconf('application_server', 'uwsgi_processes', $uwsgi_default_processes)
|
||||
$somaxconn = 2 * Integer($uwsgi_listen_backlog_limit)
|
||||
file { '/etc/zulip/uwsgi.ini':
|
||||
ensure => file,
|
||||
require => Package[supervisor],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/uwsgi.ini.template.erb'),
|
||||
notify => Service[$zulip::common::supervisor_service],
|
||||
}
|
||||
zulip::sysctl { 'uwsgi':
|
||||
comment => 'Allow larger listen backlog',
|
||||
key => 'net.core.somaxconn',
|
||||
value => $somaxconn,
|
||||
}
|
||||
|
||||
file { [
|
||||
'/home/zulip/tornado',
|
||||
'/home/zulip/prod-static',
|
||||
'/home/zulip/deployments',
|
||||
'/srv/zulip-locks',
|
||||
'/srv/zulip-emoji-cache',
|
||||
'/srv/zulip-uploaded-files-cache',
|
||||
]:
|
||||
ensure => directory,
|
||||
owner => 'zulip',
|
||||
group => 'zulip',
|
||||
mode => '0755',
|
||||
}
|
||||
# /home/zulip/uploads is documented as something administrators may
|
||||
# replace with a symlink to a different storage location, so we use
|
||||
# an exec with a `test -d` guard (which follows symlinks) rather
|
||||
# than a file resource that would replace the symlink.
|
||||
exec { 'create-uploads-dir':
|
||||
command => 'install -d -o zulip -g zulip -m 0755 /home/zulip/uploads',
|
||||
unless => 'test -d /home/zulip/uploads',
|
||||
path => '/usr/bin:/bin',
|
||||
}
|
||||
file { [
|
||||
'/var/log/zulip/queue_error',
|
||||
'/var/log/zulip/queue_stats',
|
||||
]:
|
||||
ensure => directory,
|
||||
owner => 'zulip',
|
||||
group => 'zulip',
|
||||
mode => '0750',
|
||||
}
|
||||
$access_log_retention_days = zulipconf('application_server','access_log_retention_days', 14)
|
||||
file { '/etc/logrotate.d/zulip':
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/logrotate/zulip.template.erb'),
|
||||
}
|
||||
|
||||
zulip::nagios_plugins {'zulip_app_frontend': }
|
||||
|
||||
# This cron job does nothing unless RATE_LIMIT_TOR_TOGETHER is enabled.
|
||||
zulip::cron { 'fetch-tor-exit-nodes':
|
||||
minute => '17',
|
||||
}
|
||||
# This was originally added with a typo in the name.
|
||||
file { '/etc/cron.d/fetch-for-exit-nodes':
|
||||
ensure => absent,
|
||||
}
|
||||
}
|
||||
88
puppet/zulip/manifests/app_frontend_once.pp
Normal file
88
puppet/zulip/manifests/app_frontend_once.pp
Normal file
@@ -0,0 +1,88 @@
|
||||
# Cron jobs and other tools that should run on only one Zulip server
|
||||
# in a cluster.
|
||||
|
||||
class zulip::app_frontend_once {
|
||||
include zulip::hooks::send_zulip_update_announcements
|
||||
|
||||
$proxy_host = zulipconf('http_proxy', 'host', 'localhost')
|
||||
$proxy_port = zulipconf('http_proxy', 'port', '4750')
|
||||
if $proxy_host != '' and $proxy_port != '' {
|
||||
$proxy = "http://${proxy_host}:${proxy_port}"
|
||||
} else {
|
||||
$proxy = ''
|
||||
}
|
||||
file { "${zulip::common::supervisor_conf_dir}/zulip-once.conf":
|
||||
ensure => file,
|
||||
require => [Package[supervisor], Exec['stage_updated_sharding']],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/supervisor/zulip-once.conf.template.erb'),
|
||||
notify => Service[$zulip::common::supervisor_service],
|
||||
}
|
||||
|
||||
# Every-hour
|
||||
zulip::cron { 'update-analytics-counts':
|
||||
minute => '5',
|
||||
}
|
||||
zulip::cron { 'check-analytics-state':
|
||||
minute => '30',
|
||||
}
|
||||
zulip::cron { 'promote-new-full-members':
|
||||
minute => '35',
|
||||
}
|
||||
zulip::cron { 'send_zulip_update_announcements':
|
||||
minute => '47',
|
||||
}
|
||||
|
||||
# Daily
|
||||
zulip::cron { 'soft-deactivate-users':
|
||||
hour => '5',
|
||||
minute => '0',
|
||||
manage => 'soft_deactivate_users -d',
|
||||
}
|
||||
zulip::cron { 'delete-old-unclaimed-attachments':
|
||||
hour => '5',
|
||||
minute => '0',
|
||||
manage => 'delete_old_unclaimed_attachments -f',
|
||||
}
|
||||
# If changing the time of this job, then the global time string
|
||||
# generated by demo_organizations.get_scheduled_deletion_global_time
|
||||
# needs to be updated as well.
|
||||
zulip::cron { 'archive-messages':
|
||||
hour => '6',
|
||||
minute => '0',
|
||||
}
|
||||
zulip::cron { 'send-digest-emails':
|
||||
hour => '18',
|
||||
minute => '0',
|
||||
manage => 'enqueue_digest_emails',
|
||||
}
|
||||
# Most deploys will re-run this for all streams, daily; large
|
||||
# deploys may set this to a number >= 25 to only update streams with
|
||||
# recent potential changes.
|
||||
$update_subscriber_count_incremental = zulipconf('application_server', 'update_subscriber_count_incremental', '')
|
||||
if $update_subscriber_count_incremental != '' {
|
||||
$update_subscriber_count_arg = " --since ${update_subscriber_count_incremental}"
|
||||
} else {
|
||||
$update_subscriber_count_arg = ''
|
||||
}
|
||||
zulip::cron { 'update_subscriber_counts':
|
||||
hour => '6',
|
||||
minute => '0',
|
||||
manage => "update_subscriber_counts${update_subscriber_count_arg}"
|
||||
}
|
||||
zulip::cron { 'clearsessions':
|
||||
hour => '22',
|
||||
minute => '22',
|
||||
}
|
||||
|
||||
# Weekly
|
||||
zulip::cron { 'update-channel-recently-active-status':
|
||||
hour => '5',
|
||||
minute => '0',
|
||||
dow => '0',
|
||||
manage => 'update_channel_recently_active_status',
|
||||
}
|
||||
|
||||
}
|
||||
7
puppet/zulip/manifests/apt_repository.pp
Normal file
7
puppet/zulip/manifests/apt_repository.pp
Normal file
@@ -0,0 +1,7 @@
|
||||
class zulip::apt_repository {
|
||||
$setup_apt_repo_file = "${facts['zulip_scripts_path']}/lib/setup-apt-repo"
|
||||
exec{'setup_apt_repo':
|
||||
command => "bash -c '${setup_apt_repo_file}'",
|
||||
unless => "bash -c '${setup_apt_repo_file} --verify'",
|
||||
}
|
||||
}
|
||||
53
puppet/zulip/manifests/camo.pp
Normal file
53
puppet/zulip/manifests/camo.pp
Normal file
@@ -0,0 +1,53 @@
|
||||
class zulip::camo (String $listen_address = '0.0.0.0') {
|
||||
$version = $zulip::common::versions['go-camo']['version']
|
||||
$goversion = $zulip::common::versions['go-camo']['goversion']
|
||||
$dir = "/srv/zulip-go-camo-${version}"
|
||||
$bin = "${dir}/bin/go-camo"
|
||||
|
||||
zulip::external_dep { 'go-camo':
|
||||
version => $version,
|
||||
url => "https://github.com/cactus/go-camo/releases/download/v${version}/go-camo-${version}.go${goversion}.linux-${zulip::common::goarch}.tar.gz",
|
||||
tarball_prefix => "go-camo-${version}",
|
||||
bin => [$bin],
|
||||
cleanup_after => [Service[supervisor]],
|
||||
}
|
||||
|
||||
# We would like to not waste resources by going through Smokescreen,
|
||||
# as go-camo already prohibits private-IP access; but a
|
||||
# non-Smokescreen exit proxy may be required to access the public
|
||||
# Internet. The `enable_for_camo` flag, if it exists, can override
|
||||
# our guess, in either direction.
|
||||
$proxy_host = zulipconf('http_proxy', 'host', 'localhost')
|
||||
$proxy_port = zulipconf('http_proxy', 'port', '4750')
|
||||
$proxy_is_smokescreen = ($proxy_host in ['localhost', '127.0.0.1', '::1']) and ($proxy_port == '4750')
|
||||
$camo_use_proxy = zulipconf('http_proxy', 'enable_for_camo', !$proxy_is_smokescreen)
|
||||
if $camo_use_proxy {
|
||||
if $proxy_is_smokescreen {
|
||||
include zulip::smokescreen
|
||||
}
|
||||
|
||||
if $proxy_host != '' and $proxy_port != '' {
|
||||
$proxy = "http://${proxy_host}:${proxy_port}"
|
||||
} else {
|
||||
$proxy = ''
|
||||
}
|
||||
} else {
|
||||
$proxy = ''
|
||||
}
|
||||
|
||||
$zulip_version = $facts['zulip_version']
|
||||
$external_uri = pick(get_django_setting_slow('ROOT_DOMAIN_URI'), 'https://zulip.com')
|
||||
file { "${zulip::common::supervisor_conf_dir}/go-camo.conf":
|
||||
ensure => file,
|
||||
require => [
|
||||
Package[supervisor],
|
||||
File[$bin],
|
||||
File['/usr/local/bin/secret-env-wrapper'],
|
||||
],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/supervisor/go-camo.conf.erb'),
|
||||
notify => Service[supervisor],
|
||||
}
|
||||
}
|
||||
12
puppet/zulip/manifests/certbot.pp
Normal file
12
puppet/zulip/manifests/certbot.pp
Normal file
@@ -0,0 +1,12 @@
|
||||
class zulip::certbot {
|
||||
package { 'certbot':
|
||||
ensure => installed,
|
||||
}
|
||||
file { ['/etc/letsencrypt/renewal-hooks', '/etc/letsencrypt/renewal-hooks/deploy']:
|
||||
ensure => directory,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
require => Package[certbot],
|
||||
}
|
||||
}
|
||||
224
puppet/zulip/manifests/common.pp
Normal file
224
puppet/zulip/manifests/common.pp
Normal file
@@ -0,0 +1,224 @@
|
||||
class zulip::common {
|
||||
# Common parameters
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {
|
||||
$nagios_plugins = 'monitoring-plugins-basic'
|
||||
$nagios_plugins_dir = '/usr/lib/nagios/plugins'
|
||||
$nginx = 'nginx-full'
|
||||
$supervisor_system_conf_dir = '/etc/supervisor/conf.d'
|
||||
$supervisor_conf_file = '/etc/supervisor/supervisord.conf'
|
||||
$supervisor_service = 'supervisor'
|
||||
$supervisor_start = '/usr/sbin/service supervisor start'
|
||||
$supervisor_reload = @(EOT)
|
||||
# The init script's timeout waiting for supervisor is shorter
|
||||
# than supervisor's timeout waiting for its programs, so we need
|
||||
# to ask supervisor to stop its programs first.
|
||||
supervisorctl stop all &&
|
||||
service supervisor restart &&
|
||||
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=877086
|
||||
# "restart" is actually "stop" under sysvinit
|
||||
{ service supervisor start || true; } &&
|
||||
service supervisor status
|
||||
| EOT
|
||||
$supervisor_status = '/usr/sbin/service supervisor status'
|
||||
}
|
||||
'RedHat': {
|
||||
$nagios_plugins = 'nagios-plugins'
|
||||
$nagios_plugins_dir = '/usr/lib64/nagios/plugins'
|
||||
$nginx = 'nginx'
|
||||
$supervisor_system_conf_dir = '/etc/supervisord.d/conf.d'
|
||||
$supervisor_conf_file = '/etc/supervisord.conf'
|
||||
$supervisor_service = 'supervisord'
|
||||
$supervisor_start = 'systemctl start supervisord'
|
||||
$supervisor_reload = 'systemctl reload supervisord'
|
||||
$supervisor_status = 'systemctl status supervisord'
|
||||
}
|
||||
default: {
|
||||
fail('osfamily not supported')
|
||||
}
|
||||
}
|
||||
$supervisor_conf_dir = "${supervisor_system_conf_dir}/zulip"
|
||||
|
||||
if $facts['container_memory_limit_mb'] {
|
||||
$total_memory_mb = Integer($facts['container_memory_limit_mb'])
|
||||
} else {
|
||||
$total_memory_mb = Integer($facts['memory']['system']['total_bytes'] / 1024 / 1024)
|
||||
}
|
||||
|
||||
$goarch = $facts['os']['architecture'] ? {
|
||||
'amd64' => 'amd64',
|
||||
'aarch64' => 'arm64',
|
||||
}
|
||||
|
||||
$versions = {
|
||||
# https://github.com/cactus/go-camo/releases
|
||||
'go-camo' => {
|
||||
'version' => '2.7.3',
|
||||
'goversion' => '1260',
|
||||
'sha256' => {
|
||||
'amd64' => 'd1071300b6692d194a439cb4e380ff95e1f4d894e2318bf47aaec077722370b7',
|
||||
'aarch64' => '22afef25d43cb0cb44dac100ec5602846bd66de0d9d3a3c6fc2c98eae82e6639',
|
||||
},
|
||||
},
|
||||
|
||||
# https://go.dev/dl/
|
||||
'golang' => {
|
||||
'version' => '1.26.0',
|
||||
'sha256' => {
|
||||
'amd64' => 'aac1b08a0fb0c4e0a7c1555beb7b59180b05dfc5a3d62e40e9de90cd42f88235',
|
||||
'aarch64' => 'bd03b743eb6eb4193ea3c3fd3956546bf0e3ca5b7076c8226334afe6b75704cd',
|
||||
},
|
||||
},
|
||||
|
||||
# https://github.com/stripe/smokescreen/tags
|
||||
'smokescreen-src' => {
|
||||
'version' => '464b1115f802cbd91ffee555a41e71546646d396',
|
||||
# Source code, so arch-invariant sha256
|
||||
'sha256' => '800d455bbdd23df0587b72e05e9bb51ce80d0f5816bd1b7070c2c26f4340cfca',
|
||||
},
|
||||
|
||||
# https://github.com/tus/tusd/releases
|
||||
# Keep in sync with tools/setup/install-tusd
|
||||
'tusd' => {
|
||||
'version' => '2.9.1',
|
||||
'sha256' => {
|
||||
'amd64' => '140066be543e40493abd1fda1d1e33ab7fa0e8b9a61d247130f6777e64bf28f6',
|
||||
'aarch64' => 'c9c46eba6e46b8062f863af6a5423f6f72b5157939d967087cd9b6d7c6bd19cf',
|
||||
},
|
||||
},
|
||||
|
||||
# https://github.com/wal-g/wal-g/releases
|
||||
'wal-g' => {
|
||||
'version' => '3.0.8',
|
||||
'sha256' => {
|
||||
'amd64' => 'b0df1b484035eb5f131db7bbd303d1a460391848fdcce34ba1e0a564cca493e9', # wal-g-pg-22.04-amd64.tar.gz
|
||||
'aarch64' => '048b5033c34c1487dc1fccc0572db030e323230984ccc093e80e4e4081c797d1', # wal-g-pg-22.04-aarch64.tar.gz
|
||||
},
|
||||
},
|
||||
|
||||
### kandra packages
|
||||
|
||||
# https://docs.aws.amazon.com/rolesanywhere/latest/userguide/credential-helper.html
|
||||
'aws_signing_helper' => {
|
||||
'version' => '1.7.3',
|
||||
'sha256' => {
|
||||
'amd64' => 'ef609ae021e86a2778b63dc80f4280033fcb1450bddb8b234b4ccd30f917ed21',
|
||||
'aarch64' => '7aaf8b3a4ceac464931dec27bcfd58e4facc93fb48402381e3f96bb45de5a356',
|
||||
},
|
||||
},
|
||||
|
||||
# https://release-registry.services.sentry.io/apps/sentry-cli/latest
|
||||
'sentry-cli' => {
|
||||
'version' => '2.58.4',
|
||||
'sha256' => {
|
||||
'amd64' => 'a4932b4315b192b3d037678a16eb2a5a8731609f671fc4008e643b85c3c74cb6',
|
||||
'aarch64' => '672cb986b0c5d84ef724f39b3aa189be802bceb8bc7dc8c5776a0ca90fcf41bd',
|
||||
},
|
||||
},
|
||||
|
||||
# https://grafana.com/grafana/download?edition=oss
|
||||
'grafana' => {
|
||||
'version' => '12.3.3',
|
||||
'sha256' => {
|
||||
'amd64' => 'd69b850d02903bcfe27289661c5b0b1b9a67d0bf0c42d344d55d0e63e62a7bda',
|
||||
'aarch64' => 'a2a50f6a63c89c59914b604482500de2648d12ee266d8eac8886815710482614',
|
||||
},
|
||||
},
|
||||
|
||||
# https://github.com/fstab/grok_exporter/tags
|
||||
'grok_exporter' => {
|
||||
'version' => '1.0.0.RC5',
|
||||
'sha256' => {
|
||||
'amd64' => 'b8771a6d7ca8447c222548d6cb8b2f8ee058b55bfd1801c2f6eb739534df5ded',
|
||||
# No aarch64 builds
|
||||
},
|
||||
},
|
||||
|
||||
# https://prometheus.io/download/#node_exporter
|
||||
'node_exporter' => {
|
||||
'version' => '1.10.2',
|
||||
'sha256' => {
|
||||
'amd64' => 'c46e5b6f53948477ff3a19d97c58307394a29fe64a01905646f026ddc32cb65b',
|
||||
'aarch64' => 'de69ec8341c8068b7c8e4cfe3eb85065d24d984a3b33007f575d307d13eb89a6',
|
||||
},
|
||||
},
|
||||
|
||||
# https://github.com/prometheus-community/postgres_exporter/tags
|
||||
'postgres_exporter' => {
|
||||
'version' => '0.19.0',
|
||||
'sha256' => {
|
||||
'amd64' => '11033f9016d0c5a5b91742d17aa37490b170f055ec1bb0d7d69509d223952ed0',
|
||||
'aarch64' => '8a2f55b1a00694758ea4bfd96a8bb814c1b279cf9d233a24fecd28a9e87c6280',
|
||||
},
|
||||
},
|
||||
|
||||
# https://github.com/prometheus-community/postgres_exporter/pull/843
|
||||
'postgres_exporter-src' => {
|
||||
'version' => '86a2b77aa522f57a136d04ffa33f0e46713d1925',
|
||||
'sha256' => '03b3a4e794b6c01e911c3f7f4d77a2889d6946e2245f8d12d14844c29554a884',
|
||||
},
|
||||
|
||||
# https://github.com/ncabatoff/process-exporter/releases
|
||||
'process_exporter' => {
|
||||
'version' => '0.8.7',
|
||||
'sha256' => {
|
||||
'amd64' => '6d274cca5e94c6a25e55ec05762a472561859ce0a05b984aaedb67dd857ceee2',
|
||||
'aarch64' => '4a2502f290323e57eeeb070fc10e64047ad0cd838ae5a1b347868f75667b5ab0',
|
||||
}
|
||||
},
|
||||
|
||||
# https://prometheus.io/download/#prometheus
|
||||
'prometheus' => {
|
||||
'version' => '3.9.1',
|
||||
'sha256' => {
|
||||
'amd64' => '86a6999dd6aacbd994acde93c77cfa314d4be1c8e7b7c58f444355c77b32c584',
|
||||
'aarch64' => '4e7f291e527d8aca912a995c183128388c9e048065aff84f74f5a55c9bef3793',
|
||||
},
|
||||
},
|
||||
|
||||
# https://github.com/prometheus/pushgateway/releases
|
||||
'pushgateway' => {
|
||||
'version' => '1.11.2',
|
||||
'sha256' => {
|
||||
'amd64' => '2ec72315e150dda071fdeef09360780a386a67e5207ebaa53bb18f2f1a3b89cf',
|
||||
'aarch64' => 'b3fb835dbb0a29b1d6f9cd7ae3568a5615e59b96f8787965248cea67163d4db1',
|
||||
},
|
||||
},
|
||||
|
||||
# https://github.com/oliver006/redis_exporter/releases
|
||||
'redis_exporter' => {
|
||||
'version' => '1.81.0',
|
||||
'sha256' => {
|
||||
'amd64' => 'd2f4740fa81e1a274ca99677783f3fe1544f6022fed997d4cd632d01a8eff1bb',
|
||||
'aarch64' => '038d0d2d8f044075cc97acd323c16a8c3628f08ef7d186601fedaa38b71aad8f',
|
||||
},
|
||||
},
|
||||
|
||||
# https://github.com/alexmv/teleport-sd/releases
|
||||
'teleport-sd' => {
|
||||
'version' => '0.1.0',
|
||||
'sha256' => {
|
||||
'amd64' => '2128d78dd602c6a24c13fbd759bf19d0c3cebaa732ad0824019ced53163cf455',
|
||||
'aarch64' => 'df93ba9510cb8898ba0f15b293d7c6877173a8d06b1f284dcc42ffc7ff32978e',
|
||||
},
|
||||
},
|
||||
|
||||
# https://github.com/timonwong/uwsgi_exporter/releases
|
||||
'uwsgi_exporter' => {
|
||||
'version' => '1.3.0',
|
||||
'sha256' => {
|
||||
'amd64' => 'f83411b508676237bbd1b791c1bdc043a68bf914c7e48e005e2e295255f9245f',
|
||||
'aarch64' => 'ab7c9298d2fe5c5f58e3fe7c905929e93979d2b3b11c75eb8ba6ccc7a547238c',
|
||||
},
|
||||
},
|
||||
|
||||
# https://vector.dev/download/
|
||||
'vector' => {
|
||||
'version' => '0.53.0',
|
||||
'sha256' => {
|
||||
'amd64' => '5460f93310eb59cc9e7ee7717a179f1bf2ff8fbc046b7e3eb625159a5e8714bb',
|
||||
'aarch64' => '2722dec4f5c358793caeb86b4f4febd047b37a2d688af181e8264a764ecd28e5',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
50
puppet/zulip/manifests/cron.pp
Normal file
50
puppet/zulip/manifests/cron.pp
Normal file
@@ -0,0 +1,50 @@
|
||||
# @summary Install a cron file into /etc/cron.d
|
||||
#
|
||||
define zulip::cron(
|
||||
String $minute,
|
||||
String $hour = '*',
|
||||
String $dow = '*',
|
||||
String $user = 'zulip',
|
||||
Optional[String] $command = undef,
|
||||
Optional[String] $manage = undef,
|
||||
Boolean $use_proxy = true,
|
||||
) {
|
||||
if $use_proxy {
|
||||
$proxy_host = zulipconf('http_proxy', 'host', 'localhost')
|
||||
$proxy_port = zulipconf('http_proxy', 'port', '4750')
|
||||
if $proxy_host != '' and $proxy_port != '' {
|
||||
$proxy = "http://${proxy_host}:${proxy_port}"
|
||||
} else {
|
||||
$proxy = ''
|
||||
}
|
||||
} else {
|
||||
$proxy = ''
|
||||
}
|
||||
|
||||
$dsn = zulipconf('sentry', 'project_dsn', '')
|
||||
if $dsn != '' {
|
||||
include zulip::sentry_cli
|
||||
$environment = zulipconf('machine', 'deploy_type', 'development')
|
||||
$sentry = "sentry-cli monitors run -e ${environment} --schedule '${minute} ${hour} * * ${dow}' ${title} -- "
|
||||
$cron_require = [File['/usr/local/bin/sentry-cli']]
|
||||
} else {
|
||||
$sentry = ''
|
||||
$cron_require = []
|
||||
}
|
||||
if $command != undef {
|
||||
$run = "${sentry}${command}"
|
||||
} elsif $manage != undef {
|
||||
$run = "cd /home/zulip/deployments/current/ && ${sentry}./manage.py ${manage} >/dev/null"
|
||||
} else {
|
||||
$underscores = regsubst($title, '-', '_', 'G')
|
||||
$run = "cd /home/zulip/deployments/current/ && ${sentry}./manage.py ${underscores} >/dev/null"
|
||||
}
|
||||
file { "/etc/cron.d/${title}":
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/cron.template.erb'),
|
||||
require => $cron_require,
|
||||
}
|
||||
}
|
||||
4
puppet/zulip/manifests/dockervoyager.pp
Normal file
4
puppet/zulip/manifests/dockervoyager.pp
Normal file
@@ -0,0 +1,4 @@
|
||||
# @summary Temporary shim for docker all-in-one profile
|
||||
class zulip::dockervoyager {
|
||||
include zulip::profile::docker
|
||||
}
|
||||
69
puppet/zulip/manifests/external_dep.pp
Normal file
69
puppet/zulip/manifests/external_dep.pp
Normal file
@@ -0,0 +1,69 @@
|
||||
define zulip::external_dep(
|
||||
String $version,
|
||||
String $url,
|
||||
String $tarball_prefix = '',
|
||||
String $sha256 = '',
|
||||
String $mode = '0755',
|
||||
Array[String] $bin = [],
|
||||
Array[Type[Resource]] $cleanup_after = [],
|
||||
) {
|
||||
$arch = $facts['os']['architecture']
|
||||
if $sha256 == '' {
|
||||
if $zulip::common::versions[$title]['sha256'] =~ Hash {
|
||||
$sha256_filled = $zulip::common::versions[$title]['sha256'][$arch]
|
||||
if $sha256_filled == undef {
|
||||
err("No sha256 found for ${title} for architecture ${arch}")
|
||||
fail()
|
||||
}
|
||||
} else {
|
||||
# For things like source code which are arch-invariant
|
||||
$sha256_filled = $zulip::common::versions[$title]['sha256']
|
||||
}
|
||||
} else {
|
||||
$sha256_filled = $sha256
|
||||
}
|
||||
|
||||
$path = "/srv/zulip-${title}-${version}"
|
||||
|
||||
if $tarball_prefix == '' {
|
||||
zulip::sha256_file_to { $title:
|
||||
url => $url,
|
||||
sha256 => $sha256_filled,
|
||||
install_to => $path,
|
||||
notify => Exec["Cleanup ${title}"],
|
||||
}
|
||||
file { $path:
|
||||
ensure => file,
|
||||
require => Zulip::Sha256_File_To[$title],
|
||||
before => Exec["Cleanup ${title}"],
|
||||
mode => $mode,
|
||||
}
|
||||
} else {
|
||||
zulip::sha256_tarball_to { $title:
|
||||
url => $url,
|
||||
sha256 => $sha256_filled,
|
||||
install_from => $tarball_prefix,
|
||||
install_to => $path,
|
||||
notify => Exec["Cleanup ${title}"],
|
||||
}
|
||||
file { $path:
|
||||
ensure => present,
|
||||
require => Zulip::Sha256_Tarball_To[$title],
|
||||
before => Exec["Cleanup ${title}"],
|
||||
}
|
||||
file { $bin:
|
||||
ensure => file,
|
||||
require => [File[$path], Zulip::Sha256_Tarball_To[$title]],
|
||||
before => Exec["Cleanup ${title}"],
|
||||
mode => $mode,
|
||||
}
|
||||
}
|
||||
|
||||
exec { "Cleanup ${title}":
|
||||
refreshonly => true,
|
||||
provider => shell,
|
||||
onlyif => "ls -d /srv/zulip-${title}-* | grep -xv '${path}'",
|
||||
command => "ls -d /srv/zulip-${title}-* | grep -xv '${path}' | xargs rm -r",
|
||||
require => $cleanup_after,
|
||||
}
|
||||
}
|
||||
13
puppet/zulip/manifests/golang.pp
Normal file
13
puppet/zulip/manifests/golang.pp
Normal file
@@ -0,0 +1,13 @@
|
||||
# @summary go compiler and tools
|
||||
#
|
||||
class zulip::golang {
|
||||
$version = $zulip::common::versions['golang']['version']
|
||||
$dir = "/srv/zulip-golang-${version}"
|
||||
$bin = "${dir}/bin/go"
|
||||
|
||||
zulip::external_dep { 'golang':
|
||||
version => $version,
|
||||
url => "https://go.dev/dl/go${version}.linux-${zulip::common::goarch}.tar.gz",
|
||||
tarball_prefix => 'go',
|
||||
}
|
||||
}
|
||||
16
puppet/zulip/manifests/hooks/base.pp
Normal file
16
puppet/zulip/manifests/hooks/base.pp
Normal file
@@ -0,0 +1,16 @@
|
||||
# @summary Install sentry-cli binary and pre/post deploy hooks
|
||||
#
|
||||
class zulip::hooks::base {
|
||||
file { [
|
||||
'/etc/zulip/hooks',
|
||||
'/etc/zulip/hooks/common',
|
||||
'/etc/zulip/hooks/pre-deploy.d',
|
||||
'/etc/zulip/hooks/post-deploy.d',
|
||||
]:
|
||||
ensure => directory,
|
||||
owner => 'zulip',
|
||||
group => 'zulip',
|
||||
mode => '0755',
|
||||
tag => ['hooks'],
|
||||
}
|
||||
}
|
||||
14
puppet/zulip/manifests/hooks/file.pp
Normal file
14
puppet/zulip/manifests/hooks/file.pp
Normal file
@@ -0,0 +1,14 @@
|
||||
# @summary Install a static hook file
|
||||
#
|
||||
define zulip::hooks::file() {
|
||||
include zulip::hooks::base
|
||||
|
||||
file { "/etc/zulip/hooks/${title}":
|
||||
ensure => file,
|
||||
mode => '0755',
|
||||
owner => 'zulip',
|
||||
group => 'zulip',
|
||||
source => "puppet:///modules/zulip/hooks/${title}",
|
||||
tag => ['hooks'],
|
||||
}
|
||||
}
|
||||
9
puppet/zulip/manifests/hooks/push_git_ref.pp
Normal file
9
puppet/zulip/manifests/hooks/push_git_ref.pp
Normal file
@@ -0,0 +1,9 @@
|
||||
# @summary Push the merge_base to a git repo after deploy
|
||||
#
|
||||
class zulip::hooks::push_git_ref {
|
||||
include zulip::hooks::base
|
||||
|
||||
zulip::hooks::file { [
|
||||
'post-deploy.d/push_git_ref.hook',
|
||||
]: }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# @summary Send zuip update announcements after deploy
|
||||
#
|
||||
class zulip::hooks::send_zulip_update_announcements {
|
||||
include zulip::hooks::base
|
||||
|
||||
zulip::hooks::file { [
|
||||
'post-deploy.d/send_zulip_update_announcements.hook',
|
||||
]: }
|
||||
}
|
||||
12
puppet/zulip/manifests/hooks/sentry.pp
Normal file
12
puppet/zulip/manifests/hooks/sentry.pp
Normal file
@@ -0,0 +1,12 @@
|
||||
# @summary Install Sentry pre/post deploy hooks
|
||||
#
|
||||
class zulip::hooks::sentry {
|
||||
include zulip::hooks::base
|
||||
include zulip::sentry_cli
|
||||
|
||||
zulip::hooks::file { [
|
||||
'common/sentry.sh',
|
||||
'pre-deploy.d/sentry.hook',
|
||||
'post-deploy.d/sentry.hook',
|
||||
]: }
|
||||
}
|
||||
7
puppet/zulip/manifests/hooks/zulip_common.pp
Normal file
7
puppet/zulip/manifests/hooks/zulip_common.pp
Normal file
@@ -0,0 +1,7 @@
|
||||
# @summary zulip_notify common file
|
||||
#
|
||||
class zulip::hooks::zulip_common {
|
||||
include zulip::hooks::base
|
||||
|
||||
zulip::hooks::file { 'common/zulip_notify.sh': }
|
||||
}
|
||||
11
puppet/zulip/manifests/hooks/zulip_notify.pp
Normal file
11
puppet/zulip/manifests/hooks/zulip_notify.pp
Normal file
@@ -0,0 +1,11 @@
|
||||
# @summary Install hook that notifies when a deploy starts/stops
|
||||
#
|
||||
class zulip::hooks::zulip_notify {
|
||||
include zulip::hooks::base
|
||||
include zulip::hooks::zulip_common
|
||||
|
||||
zulip::hooks::file { [
|
||||
'pre-deploy.d/zulip_notify.hook',
|
||||
'post-deploy.d/zulip_notify.hook',
|
||||
]: }
|
||||
}
|
||||
34
puppet/zulip/manifests/local_mailserver.pp
Normal file
34
puppet/zulip/manifests/local_mailserver.pp
Normal file
@@ -0,0 +1,34 @@
|
||||
class zulip::local_mailserver {
|
||||
include zulip::snakeoil
|
||||
include zulip::certbot
|
||||
|
||||
if zulipconf('postfix', 'uninstall', true) {
|
||||
package { 'postfix':
|
||||
# TODO/compatibility: We can remove this when upgrading directly
|
||||
# from 10.x is no longer possible. We do not use "purged" here,
|
||||
# since that would remove config files, which users may have had
|
||||
# installed.
|
||||
ensure => absent,
|
||||
before => Service[$zulip::common::supervisor_service],
|
||||
}
|
||||
}
|
||||
$listen = zulipconf('email_gateway', 'listen', '')
|
||||
file { "${zulip::common::supervisor_conf_dir}/email-mirror.conf":
|
||||
ensure => file,
|
||||
require => [
|
||||
Package[supervisor],
|
||||
],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/supervisor/email-mirror.conf.template.erb'),
|
||||
notify => Service[$zulip::common::supervisor_service],
|
||||
}
|
||||
file { '/etc/letsencrypt/renewal-hooks/deploy/055-email-server.sh':
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
source => 'puppet:///modules/zulip/letsencrypt/055-email-server.sh',
|
||||
}
|
||||
}
|
||||
16
puppet/zulip/manifests/localhost_camo.pp
Normal file
16
puppet/zulip/manifests/localhost_camo.pp
Normal file
@@ -0,0 +1,16 @@
|
||||
class zulip::localhost_camo {
|
||||
class { 'zulip::camo':
|
||||
listen_address => '127.0.0.1',
|
||||
}
|
||||
|
||||
# Install nginx configuration to run camo locally
|
||||
file { '/etc/nginx/zulip-include/app.d/camo.conf':
|
||||
ensure => file,
|
||||
require => Package['nginx-full'],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
notify => Service['nginx'],
|
||||
source => 'puppet:///modules/zulip/nginx/zulip-include-app.d/camo.conf',
|
||||
}
|
||||
}
|
||||
11
puppet/zulip/manifests/localhost_sso.pp
Normal file
11
puppet/zulip/manifests/localhost_sso.pp
Normal file
@@ -0,0 +1,11 @@
|
||||
class zulip::localhost_sso {
|
||||
file { '/etc/nginx/zulip-include/app.d/external-sso.conf':
|
||||
ensure => file,
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
notify => Service['nginx'],
|
||||
source => 'puppet:///modules/zulip/nginx/zulip-include-app.d/external-sso.conf',
|
||||
}
|
||||
}
|
||||
14
puppet/zulip/manifests/nagios_plugins.pp
Normal file
14
puppet/zulip/manifests/nagios_plugins.pp
Normal file
@@ -0,0 +1,14 @@
|
||||
# @summary Installs a subdirectory from puppet/zulip/files/nagios_plugins/
|
||||
define zulip::nagios_plugins () {
|
||||
include zulip::common
|
||||
|
||||
file { "${zulip::common::nagios_plugins_dir}/${name}":
|
||||
require => Package[$zulip::common::nagios_plugins],
|
||||
recurse => true,
|
||||
purge => true,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
source => "puppet:///modules/zulip/nagios_plugins/${name}",
|
||||
}
|
||||
}
|
||||
120
puppet/zulip/manifests/nginx.pp
Normal file
120
puppet/zulip/manifests/nginx.pp
Normal file
@@ -0,0 +1,120 @@
|
||||
class zulip::nginx {
|
||||
include zulip::certbot
|
||||
package { $zulip::common::nginx: ensure => installed }
|
||||
package { 'ca-certificates': ensure => latest }
|
||||
|
||||
if $facts['os']['family'] == 'RedHat' {
|
||||
file { '/etc/nginx/sites-available':
|
||||
ensure => directory,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
}
|
||||
file { '/etc/nginx/sites-enabled':
|
||||
ensure => directory,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
}
|
||||
}
|
||||
|
||||
file { '/etc/nginx/zulip-include/':
|
||||
require => Package[$zulip::common::nginx],
|
||||
recurse => true,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/nginx/zulip-include-common/',
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
|
||||
file { '/etc/nginx/dhparam.pem':
|
||||
ensure => file,
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
notify => Service['nginx'],
|
||||
source => 'puppet:///modules/zulip/nginx/dhparam.pem',
|
||||
}
|
||||
|
||||
if $facts['os']['family'] == 'Debian' {
|
||||
$ca_crt = '/etc/ssl/certs/ca-certificates.crt'
|
||||
} else {
|
||||
$ca_crt = '/etc/pki/tls/certs/ca-bundle.crt'
|
||||
}
|
||||
$worker_processes = zulipconf('application_server', 'nginx_worker_processes', 'auto')
|
||||
$worker_connections = zulipconf('application_server', 'nginx_worker_connections', 10000)
|
||||
file { '/etc/nginx/nginx.conf':
|
||||
ensure => file,
|
||||
require => Package[$zulip::common::nginx, 'ca-certificates'],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
notify => Service['nginx'],
|
||||
content => template('zulip/nginx.conf.template.erb'),
|
||||
}
|
||||
|
||||
$loadbalancers = split(zulipconf('loadbalancer', 'ips', ''), ',')
|
||||
$raw_true_client_header = zulipconf('loadbalancer', 'true_client_header', '')
|
||||
$true_client_header = regsubst(downcase($raw_true_client_header), '-', '_', 'G')
|
||||
$true_client_header_from = split(zulipconf('loadbalancer', 'true_client_header_from', ''), ',')
|
||||
$lb_rejects_http_requests = zulipconf('loadbalancer', 'rejects_http_requests', false)
|
||||
file { '/etc/nginx/zulip-include/trusted-ip':
|
||||
ensure => file,
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
notify => Service['nginx'],
|
||||
content => template('zulip/nginx/trusted-ip.template.erb'),
|
||||
}
|
||||
file { '/etc/nginx/zulip-include/trusted-proto':
|
||||
ensure => file,
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
notify => Service['nginx'],
|
||||
content => template('zulip/nginx/trusted-proto.template.erb'),
|
||||
}
|
||||
|
||||
file { '/etc/nginx/uwsgi_params':
|
||||
ensure => file,
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
notify => Service['nginx'],
|
||||
source => 'puppet:///modules/zulip/nginx/uwsgi_params',
|
||||
}
|
||||
|
||||
file { '/etc/nginx/sites-enabled/default':
|
||||
ensure => absent,
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
|
||||
file { '/var/log/nginx':
|
||||
ensure => directory,
|
||||
owner => 'zulip',
|
||||
group => 'adm',
|
||||
mode => '0750',
|
||||
}
|
||||
$access_log_retention_days = zulipconf('application_server','access_log_retention_days', 14)
|
||||
file { '/etc/logrotate.d/nginx':
|
||||
ensure => file,
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/logrotate/nginx.template.erb'),
|
||||
}
|
||||
file { ['/var/lib/zulip', '/var/lib/zulip/certbot-webroot']:
|
||||
ensure => directory,
|
||||
owner => 'zulip',
|
||||
group => 'adm',
|
||||
mode => '0770',
|
||||
}
|
||||
|
||||
service { 'nginx':
|
||||
ensure => running,
|
||||
}
|
||||
}
|
||||
47
puppet/zulip/manifests/postgresql_backups.pp
Normal file
47
puppet/zulip/manifests/postgresql_backups.pp
Normal file
@@ -0,0 +1,47 @@
|
||||
# @summary Use wal-g to take daily backups of PostgreSQL
|
||||
#
|
||||
class zulip::postgresql_backups {
|
||||
include zulip::postgresql_common
|
||||
include zulip::wal_g
|
||||
|
||||
file { '/var/log/pg_backup_and_purge.log':
|
||||
ensure => file,
|
||||
owner => 'postgres',
|
||||
group => 'postgres',
|
||||
mode => '0644',
|
||||
}
|
||||
file { '/usr/local/bin/pg_backup_and_purge':
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'postgres',
|
||||
mode => '0754',
|
||||
source => 'puppet:///modules/zulip/postgresql/pg_backup_and_purge',
|
||||
require => [
|
||||
File['/usr/local/bin/env-wal-g'],
|
||||
Package[
|
||||
$zulip::postgresql_common::postgresql,
|
||||
],
|
||||
],
|
||||
}
|
||||
|
||||
zulip::cron { 'pg_backup_and_purge':
|
||||
hour => '2',
|
||||
minute => '0',
|
||||
command => '/usr/local/bin/pg_backup_and_purge >/var/log/pg_backup_and_purge.log 2>&1',
|
||||
user => 'postgres',
|
||||
require => [
|
||||
File['/var/log/pg_backup_and_purge.log'],
|
||||
File['/usr/local/bin/pg_backup_and_purge'],
|
||||
],
|
||||
}
|
||||
|
||||
$postgresql_backup_directory = zulipconf('postgresql', 'backups_directory', '')
|
||||
if $postgresql_backup_directory != '' {
|
||||
file { $postgresql_backup_directory:
|
||||
ensure => directory,
|
||||
owner => 'postgres',
|
||||
group => 'postgres',
|
||||
mode => '0600',
|
||||
}
|
||||
}
|
||||
}
|
||||
80
puppet/zulip/manifests/postgresql_base.pp
Normal file
80
puppet/zulip/manifests/postgresql_base.pp
Normal file
@@ -0,0 +1,80 @@
|
||||
# Minimal shared configuration needed to run a Zulip PostgreSQL database.
|
||||
class zulip::postgresql_base {
|
||||
include zulip::postgresql_common
|
||||
include zulip::process_fts_updates
|
||||
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {
|
||||
$postgresql = "postgresql-${zulip::postgresql_common::version}"
|
||||
$postgresql_sharedir = "/usr/share/postgresql/${zulip::postgresql_common::version}"
|
||||
$postgresql_confdirs = [
|
||||
"/etc/postgresql/${zulip::postgresql_common::version}",
|
||||
"/etc/postgresql/${zulip::postgresql_common::version}/main",
|
||||
]
|
||||
$postgresql_confdir = $postgresql_confdirs[-1]
|
||||
$postgresql_datadir = "/var/lib/postgresql/${zulip::postgresql_common::version}/main"
|
||||
$tsearch_datadir = "${postgresql_sharedir}/tsearch_data"
|
||||
$pgroonga_setup_sql_path = "${postgresql_sharedir}/pgroonga_setup.sql"
|
||||
$setup_system_deps = 'setup_apt_repo'
|
||||
}
|
||||
'RedHat': {
|
||||
$postgresql = "postgresql${zulip::postgresql_common::version}"
|
||||
$postgresql_sharedir = "/usr/pgsql-${zulip::postgresql_common::version}/share"
|
||||
$postgresql_confdirs = [
|
||||
"/var/lib/pgsql/${zulip::postgresql_common::version}",
|
||||
"/var/lib/pgsql/${zulip::postgresql_common::version}/data",
|
||||
]
|
||||
$postgresql_confdir = $postgresql_confdirs[-1]
|
||||
$postgresql_datadir = "/var/lib/pgsql/${zulip::postgresql_common::version}/data"
|
||||
$tsearch_datadir = "${postgresql_sharedir}/tsearch_data/"
|
||||
$pgroonga_setup_sql_path = "${postgresql_sharedir}/pgroonga_setup.sql"
|
||||
$setup_system_deps = 'setup_yum_repo'
|
||||
}
|
||||
default: {
|
||||
fail('osfamily not supported')
|
||||
}
|
||||
}
|
||||
|
||||
file { "${tsearch_datadir}/zulip_english.stop":
|
||||
ensure => file,
|
||||
require => Package[$postgresql],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/postgresql/zulip_english.stop',
|
||||
tag => ['postgresql_upgrade'],
|
||||
}
|
||||
zulip::nagios_plugins { 'zulip_postgresql': }
|
||||
|
||||
$pgroonga = zulipconf('machine', 'pgroonga', false)
|
||||
if $pgroonga {
|
||||
# Needed for optional our full text search system
|
||||
package{"${postgresql}-pgdg-pgroonga":
|
||||
ensure => latest,
|
||||
require => [
|
||||
Package[$postgresql],
|
||||
Exec[$setup_system_deps]
|
||||
],
|
||||
tag => ['postgresql_upgrade'],
|
||||
}
|
||||
exec { 'pgroonga-config':
|
||||
require => Package["${postgresql}-pgdg-pgroonga"],
|
||||
unless => @("EOT"/$),
|
||||
test -f ${pgroonga_setup_sql_path}.applied &&
|
||||
test "$(dpkg-query --show --showformat='\${Version}' "${postgresql}-pgdg-pgroonga")" \
|
||||
= "$(cat ${pgroonga_setup_sql_path}.applied)"
|
||||
| EOT
|
||||
command => "${facts['zulip_scripts_path']}/setup/pgroonga-config ${postgresql_sharedir}",
|
||||
}
|
||||
}
|
||||
|
||||
$backups_s3_bucket = zulipsecret('secrets', 's3_backups_bucket', '')
|
||||
$backups_directory = zulipconf('postgresql', 'backups_directory', '')
|
||||
if $backups_s3_bucket != '' or $backups_directory != '' {
|
||||
include zulip::postgresql_backups
|
||||
} else {
|
||||
file { '/etc/cron.d/pg_backup_and_purge':
|
||||
ensure => absent,
|
||||
}
|
||||
}
|
||||
}
|
||||
12
puppet/zulip/manifests/postgresql_client.pp
Normal file
12
puppet/zulip/manifests/postgresql_client.pp
Normal file
@@ -0,0 +1,12 @@
|
||||
class zulip::postgresql_client {
|
||||
$version = zulipconf('postgresql', 'version', undef)
|
||||
if $version != undef {
|
||||
package { "postgresql-client-${version}":
|
||||
ensure => installed,
|
||||
}
|
||||
} else {
|
||||
package { 'postgresql-client':
|
||||
ensure => installed,
|
||||
}
|
||||
}
|
||||
}
|
||||
79
puppet/zulip/manifests/postgresql_common.pp
Normal file
79
puppet/zulip/manifests/postgresql_common.pp
Normal file
@@ -0,0 +1,79 @@
|
||||
class zulip::postgresql_common {
|
||||
include zulip::snakeoil
|
||||
$version = zulipconf('postgresql', 'version', undef)
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {
|
||||
$postgresql = "postgresql-${version}"
|
||||
$postgresql_packages = [
|
||||
# The database itself
|
||||
$postgresql,
|
||||
# tools for database monitoring; formerly ptop
|
||||
'pgtop',
|
||||
# our dictionary
|
||||
'hunspell-en-us',
|
||||
# PostgreSQL Nagios check plugin
|
||||
'check-postgres',
|
||||
]
|
||||
$postgresql_user_reqs = [
|
||||
Package[$postgresql],
|
||||
Package['ssl-cert'],
|
||||
]
|
||||
}
|
||||
'RedHat': {
|
||||
$postgresql = "postgresql${version}"
|
||||
$postgresql_packages = [
|
||||
$postgresql,
|
||||
"${postgresql}-server",
|
||||
"${postgresql}-devel",
|
||||
'pg_top',
|
||||
'hunspell-en-US',
|
||||
# exists on CentOS 6 and Fedora 29 but not CentOS 7
|
||||
# see https://pkgs.org/download/check_postgres
|
||||
# alternatively, download directly from:
|
||||
# https://bucardo.org/check_postgres/
|
||||
# 'check-postgres', # TODO
|
||||
]
|
||||
group { 'ssl-cert':
|
||||
ensure => present,
|
||||
}
|
||||
# allows ssl-cert group to read /etc/pki/tls/private
|
||||
file { '/etc/pki/tls/private':
|
||||
ensure => directory,
|
||||
mode => '0750',
|
||||
owner => 'root',
|
||||
group => 'ssl-cert',
|
||||
}
|
||||
$postgresql_user_reqs = [
|
||||
Package[$postgresql],
|
||||
Group['ssl-cert'],
|
||||
]
|
||||
}
|
||||
default: {
|
||||
fail('osfamily not supported')
|
||||
}
|
||||
}
|
||||
|
||||
zulip::safepackage { $postgresql_packages:
|
||||
ensure => installed,
|
||||
require => Exec['generate-default-snakeoil'],
|
||||
tag => ['postgresql_upgrade'],
|
||||
}
|
||||
|
||||
if $facts['os']['family'] == 'Debian' {
|
||||
# The logrotate file only created in debian-based systems
|
||||
exec { 'disable_logrotate':
|
||||
# lint:ignore:140chars
|
||||
command => '/usr/bin/dpkg-divert --rename --divert /etc/logrotate.d/postgresql-common.disabled --add /etc/logrotate.d/postgresql-common',
|
||||
# lint:endignore
|
||||
creates => '/etc/logrotate.d/postgresql-common.disabled',
|
||||
}
|
||||
}
|
||||
|
||||
# Use arcane puppet virtual resources to add postgres user to zulip group
|
||||
@user { 'postgres':
|
||||
groups => ['ssl-cert'],
|
||||
membership => minimum,
|
||||
require => $postgresql_user_reqs,
|
||||
}
|
||||
User <| title == postgres |> { groups +> 'zulip' }
|
||||
}
|
||||
38
puppet/zulip/manifests/process_fts_updates.pp
Normal file
38
puppet/zulip/manifests/process_fts_updates.pp
Normal file
@@ -0,0 +1,38 @@
|
||||
class zulip::process_fts_updates {
|
||||
include zulip::supervisor
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {
|
||||
$fts_updates_packages = [
|
||||
# Needed to run process_fts_updates
|
||||
'python3-psycopg2', # TODO: use a virtualenv instead
|
||||
]
|
||||
zulip::safepackage { $fts_updates_packages: ensure => installed }
|
||||
}
|
||||
'RedHat': {
|
||||
exec {'pip_process_fts_updates':
|
||||
command => 'python3 -m pip install psycopg2',
|
||||
}
|
||||
}
|
||||
default: {
|
||||
fail('osfamily not supported')
|
||||
}
|
||||
}
|
||||
|
||||
file { '/usr/local/bin/process_fts_updates':
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
source => 'puppet:///modules/zulip/postgresql/process_fts_updates',
|
||||
}
|
||||
|
||||
file { "${zulip::common::supervisor_conf_dir}/zulip_db.conf":
|
||||
ensure => file,
|
||||
require => [Package[supervisor], Package['python3-psycopg2']],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/supervisor/conf.d/zulip_db.conf',
|
||||
notify => Service[$zulip::common::supervisor_service],
|
||||
}
|
||||
}
|
||||
65
puppet/zulip/manifests/profile/app_frontend.pp
Normal file
65
puppet/zulip/manifests/profile/app_frontend.pp
Normal file
@@ -0,0 +1,65 @@
|
||||
# Default configuration for a Zulip app frontend
|
||||
class zulip::profile::app_frontend {
|
||||
include zulip::profile::base
|
||||
include zulip::app_frontend_base
|
||||
include zulip::app_frontend_once
|
||||
|
||||
$nginx_http_only = zulipconf('application_server', 'http_only', false)
|
||||
if $nginx_http_only {
|
||||
$nginx_listen_port = zulipconf('application_server', 'nginx_listen_port', 80)
|
||||
} else {
|
||||
$nginx_listen_port = zulipconf('application_server', 'nginx_listen_port', 443)
|
||||
}
|
||||
$nginx_server_name = zulipconf('application_server', 'nginx_server_name', '')
|
||||
$ssl_dir = $facts['os']['family'] ? {
|
||||
'Debian' => '/etc/ssl',
|
||||
'RedHat' => '/etc/pki/tls',
|
||||
}
|
||||
file { '/etc/nginx/sites-available/zulip-enterprise':
|
||||
ensure => file,
|
||||
require => Package[$zulip::common::nginx],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/nginx/zulip-enterprise.template.erb'),
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
file { '/etc/nginx/sites-enabled/zulip-enterprise':
|
||||
ensure => link,
|
||||
require => Package[$zulip::common::nginx],
|
||||
target => '/etc/nginx/sites-available/zulip-enterprise',
|
||||
notify => Service['nginx'],
|
||||
}
|
||||
|
||||
# Reload nginx after deploying a new cert.
|
||||
file { '/etc/letsencrypt/renewal-hooks/deploy/001-nginx.sh':
|
||||
# This was renumbered
|
||||
ensure => absent,
|
||||
}
|
||||
file { '/etc/letsencrypt/renewal-hooks/deploy/020-symlink.sh':
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
source => 'puppet:///modules/zulip/letsencrypt/020-symlink.sh',
|
||||
require => [
|
||||
Package[certbot],
|
||||
File['/etc/letsencrypt/renewal-hooks/deploy'],
|
||||
]
|
||||
}
|
||||
file { '/etc/letsencrypt/renewal-hooks/deploy/050-nginx.sh':
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
source => 'puppet:///modules/zulip/letsencrypt/050-nginx.sh',
|
||||
}
|
||||
|
||||
# Restart the server regularly to avoid potential memory leak problems.
|
||||
zulip::cron { 'restart-zulip':
|
||||
hour => '6',
|
||||
minute => '0',
|
||||
dow => '0',
|
||||
command => '/home/zulip/deployments/current/scripts/restart-server --fill-cache --skip-client-reloads',
|
||||
}
|
||||
}
|
||||
142
puppet/zulip/manifests/profile/base.pp
Normal file
142
puppet/zulip/manifests/profile/base.pp
Normal file
@@ -0,0 +1,142 @@
|
||||
# @summary Included only by classes that can be deployed.
|
||||
#
|
||||
# This class should only be included by classes that are intended to
|
||||
# be able to be deployed on their own host.
|
||||
class zulip::profile::base {
|
||||
include zulip::timesync
|
||||
include zulip::common
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {
|
||||
include zulip::apt_repository
|
||||
}
|
||||
'RedHat': {
|
||||
include zulip::yum_repository
|
||||
}
|
||||
default: {
|
||||
fail('osfamily not supported')
|
||||
}
|
||||
}
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {
|
||||
$base_packages = [
|
||||
# Basics
|
||||
'python3',
|
||||
'python3-yaml',
|
||||
'puppet',
|
||||
'git',
|
||||
'curl',
|
||||
'jq',
|
||||
'procps',
|
||||
# Used to read /etc/zulip/zulip.conf for `zulipconf` Puppet function
|
||||
'crudini',
|
||||
# Used for tools like sponge
|
||||
'moreutils',
|
||||
# Nagios monitoring plugins
|
||||
$zulip::common::nagios_plugins,
|
||||
# Required for using HTTPS in apt repositories.
|
||||
'apt-transport-https',
|
||||
# Needed for the cron jobs installed by Puppet
|
||||
'cron',
|
||||
# Applies log rotation config installed by Puppet
|
||||
'logrotate',
|
||||
]
|
||||
}
|
||||
'RedHat': {
|
||||
$base_packages = [
|
||||
'python3',
|
||||
'python3-pyyaml',
|
||||
'puppet',
|
||||
'git',
|
||||
'curl',
|
||||
'jq',
|
||||
'crudini',
|
||||
'moreutils',
|
||||
'nmap-ncat',
|
||||
'nagios-plugins', # there is no dummy package on CentOS 7
|
||||
'cronie',
|
||||
]
|
||||
}
|
||||
default: {
|
||||
fail('osfamily not supported')
|
||||
}
|
||||
}
|
||||
package { $base_packages: ensure => installed }
|
||||
|
||||
group { 'zulip':
|
||||
ensure => present,
|
||||
}
|
||||
|
||||
user { 'zulip':
|
||||
ensure => present,
|
||||
require => Group['zulip'],
|
||||
gid => 'zulip',
|
||||
shell => '/bin/bash',
|
||||
home => '/home/zulip',
|
||||
managehome => true,
|
||||
}
|
||||
|
||||
file { '/etc/zulip':
|
||||
ensure => directory,
|
||||
mode => '0755',
|
||||
owner => 'zulip',
|
||||
group => 'zulip',
|
||||
links => follow,
|
||||
}
|
||||
file { ['/etc/zulip/zulip.conf', '/etc/zulip/settings.py']:
|
||||
ensure => present,
|
||||
require => File['/etc/zulip'],
|
||||
mode => '0644',
|
||||
owner => 'zulip',
|
||||
group => 'zulip',
|
||||
}
|
||||
file { '/etc/zulip/zulip-secrets.conf':
|
||||
ensure => present,
|
||||
require => File['/etc/zulip'],
|
||||
mode => '0640',
|
||||
owner => 'zulip',
|
||||
group => 'zulip',
|
||||
}
|
||||
|
||||
file { '/etc/security/limits.d/zulip.conf':
|
||||
ensure => file,
|
||||
mode => '0640',
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
source => 'puppet:///modules/zulip/limits.d/zulip.conf',
|
||||
}
|
||||
file { '/etc/systemd/system.conf.d/':
|
||||
ensure => directory,
|
||||
mode => '0755',
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
}
|
||||
file { '/etc/systemd/system.conf.d/limits.conf':
|
||||
ensure => file,
|
||||
mode => '0644',
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
source => 'puppet:///modules/zulip/systemd/system.conf.d/limits.conf',
|
||||
}
|
||||
|
||||
service { 'puppet':
|
||||
ensure => stopped,
|
||||
enable => false,
|
||||
require => Package['puppet'],
|
||||
}
|
||||
|
||||
# This directory is written to by cron jobs for reading by Nagios
|
||||
file { '/var/lib/nagios_state/':
|
||||
ensure => directory,
|
||||
group => 'zulip',
|
||||
mode => '0775',
|
||||
}
|
||||
|
||||
file { '/var/log/zulip':
|
||||
ensure => directory,
|
||||
owner => 'zulip',
|
||||
group => 'zulip',
|
||||
mode => '0750',
|
||||
}
|
||||
|
||||
zulip::nagios_plugins { 'zulip_base': }
|
||||
}
|
||||
29
puppet/zulip/manifests/profile/docker.pp
Normal file
29
puppet/zulip/manifests/profile/docker.pp
Normal file
@@ -0,0 +1,29 @@
|
||||
# This class includes all the modules you need to install/run a Zulip installation
|
||||
# in a single container (without the database, memcached, Redis services).
|
||||
# The database, memcached, Redis services need to be run in separate containers.
|
||||
# Through this split of services, it is easier to scale the services to the needs.
|
||||
class zulip::profile::docker {
|
||||
include zulip::profile::base
|
||||
include zulip::profile::app_frontend
|
||||
include zulip::localhost_camo
|
||||
include zulip::local_mailserver
|
||||
include zulip::supervisor
|
||||
include zulip::process_fts_updates
|
||||
|
||||
file { "${zulip::common::supervisor_conf_dir}/cron.conf":
|
||||
ensure => file,
|
||||
require => Package[supervisor],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/supervisor/conf.d/cron.conf',
|
||||
}
|
||||
file { "${zulip::common::supervisor_conf_dir}/nginx.conf":
|
||||
ensure => file,
|
||||
require => Package[supervisor],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/supervisor/conf.d/nginx.conf',
|
||||
}
|
||||
}
|
||||
97
puppet/zulip/manifests/profile/memcached.pp
Normal file
97
puppet/zulip/manifests/profile/memcached.pp
Normal file
@@ -0,0 +1,97 @@
|
||||
class zulip::profile::memcached {
|
||||
include zulip::profile::base
|
||||
include zulip::sasl_modules
|
||||
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {
|
||||
$memcached_packages = [ 'memcached', 'sasl2-bin' ]
|
||||
$memcached_user = 'memcache'
|
||||
}
|
||||
'RedHat': {
|
||||
$memcached_packages = [ 'memcached', 'cyrus-sasl' ]
|
||||
$memcached_user = 'memcached'
|
||||
}
|
||||
default: {
|
||||
fail('osfamily not supported')
|
||||
}
|
||||
}
|
||||
package { $memcached_packages: ensure => installed }
|
||||
|
||||
$memcached_max_item_size = zulipconf('memcached', 'max_item_size', '1m')
|
||||
$memcached_size_reporting = zulipconf('memcached', 'size_reporting', false)
|
||||
$memcached_memory = zulipconf('memcached', 'memory', $zulip::common::total_memory_mb / 8)
|
||||
file { '/etc/sasl2':
|
||||
ensure => directory,
|
||||
}
|
||||
file { '/etc/sasl2/memcached-zulip-password':
|
||||
# We cache the password in this file so we can check whether it
|
||||
# changed and avoid running saslpasswd2 if it didn't.
|
||||
require => File['/etc/sasl2'],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0600',
|
||||
content => zulipsecret('secrets', 'memcached_password', ''),
|
||||
notify => Exec[generate_memcached_sasldb2],
|
||||
}
|
||||
file { '/var/lib/zulip/memcached-sasldb2.stamp':
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => '1',
|
||||
notify => Exec[generate_memcached_sasldb2],
|
||||
}
|
||||
exec { 'generate_memcached_sasldb2':
|
||||
require => [
|
||||
Package[$memcached_packages],
|
||||
Package[$zulip::sasl_modules::sasl_module_packages],
|
||||
],
|
||||
refreshonly => true,
|
||||
# Use localhost for the currently recommended MEMCACHED_USERNAME =
|
||||
# "zulip@localhost" and the hostname for compatibility with
|
||||
# MEMCACHED_USERNAME = "zulip".
|
||||
command => "bash -euc '
|
||||
rm -f /etc/sasl2/memcached-sasldb2
|
||||
saslpasswd2 -p -f /etc/sasl2/memcached-sasldb2 \
|
||||
-a memcached -u localhost zulip < /etc/sasl2/memcached-zulip-password
|
||||
saslpasswd2 -p -f /etc/sasl2/memcached-sasldb2 \
|
||||
-a memcached -u \"\$HOSTNAME\" zulip < /etc/sasl2/memcached-zulip-password
|
||||
'",
|
||||
}
|
||||
file { '/etc/sasl2/memcached-sasldb2':
|
||||
require => Exec[generate_memcached_sasldb2],
|
||||
owner => $memcached_user,
|
||||
group => $memcached_user,
|
||||
mode => '0600',
|
||||
}
|
||||
file { '/etc/sasl2/memcached.conf':
|
||||
require => File['/etc/sasl2'],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/sasl2/memcached.conf',
|
||||
notify => Service[memcached],
|
||||
}
|
||||
file { '/etc/memcached.conf':
|
||||
ensure => file,
|
||||
require => [
|
||||
Package[$memcached_packages],
|
||||
Package[$zulip::sasl_modules::sasl_module_packages]
|
||||
],
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
content => template('zulip/memcached.conf.template.erb'),
|
||||
}
|
||||
file { '/run/memcached':
|
||||
ensure => directory,
|
||||
owner => 'memcache',
|
||||
group => 'memcache',
|
||||
mode => '0755',
|
||||
require => Package[$memcached_packages],
|
||||
}
|
||||
service { 'memcached':
|
||||
ensure => running,
|
||||
subscribe => File['/etc/memcached.conf'],
|
||||
require => File['/run/memcached'],
|
||||
}
|
||||
}
|
||||
116
puppet/zulip/manifests/profile/postgresql.pp
Normal file
116
puppet/zulip/manifests/profile/postgresql.pp
Normal file
@@ -0,0 +1,116 @@
|
||||
# @summary Extends postgresql_base by tuning the configuration.
|
||||
class zulip::profile::postgresql(Boolean $start = true) {
|
||||
include zulip::profile::base
|
||||
include zulip::postgresql_base
|
||||
include zulip::systemd_daemon_reload
|
||||
|
||||
$version = $zulip::postgresql_common::version
|
||||
|
||||
if defined(Class['zulip::app_frontend_base']) {
|
||||
$total_postgres_memory_mb = zulipconf('postgresql', 'memory', $zulip::common::total_memory_mb / 2)
|
||||
} else {
|
||||
$total_postgres_memory_mb = zulipconf('postgresql', 'memory', $zulip::common::total_memory_mb)
|
||||
}
|
||||
$work_mem = zulipconf('postgresql', 'work_mem', sprintf('%dMB', $total_postgres_memory_mb / 256))
|
||||
$shared_buffers = zulipconf('postgresql', 'shared_buffers', sprintf('%dMB', $total_postgres_memory_mb / 4))
|
||||
$effective_cache_size = zulipconf('postgresql', 'effective_cache_size', sprintf('%dMB', $total_postgres_memory_mb * 3 / 4))
|
||||
$maintenance_work_mem = zulipconf('postgresql', 'maintenance_work_mem', sprintf('%dMB', min(2048, $total_postgres_memory_mb / 8)))
|
||||
|
||||
$max_worker_processes = zulipconf('postgresql', 'max_worker_processes', undef)
|
||||
$max_parallel_workers_per_gather = zulipconf('postgresql', 'max_parallel_workers_per_gather', undef)
|
||||
$max_parallel_workers = zulipconf('postgresql', 'max_parallel_workers', undef)
|
||||
$max_parallel_maintenance_workers = zulipconf('postgresql', 'max_parallel_maintenance_workers', undef)
|
||||
|
||||
$wal_buffers = zulipconf('postgresql', 'wal_buffers', undef)
|
||||
$min_wal_size = zulipconf('postgresql', 'min_wal_size', undef)
|
||||
$max_wal_size = zulipconf('postgresql', 'max_wal_size', undef)
|
||||
$random_page_cost = zulipconf('postgresql', 'random_page_cost', '1.1')
|
||||
$effective_io_concurrency = zulipconf('postgresql', 'effective_io_concurrency', undef)
|
||||
|
||||
$listen_addresses = zulipconf('postgresql', 'listen_addresses', undef)
|
||||
|
||||
$s3_backups_bucket = zulipsecret('secrets', 's3_backups_bucket', '')
|
||||
$replication_primary = zulipconf('postgresql', 'replication_primary', undef)
|
||||
$replication_user = zulipconf('postgresql', 'replication_user', undef)
|
||||
$replication_password = zulipsecret('secrets', 'postgresql_replication_password', '')
|
||||
|
||||
$ssl_cert_file = zulipconf('postgresql', 'ssl_cert_file', undef)
|
||||
$ssl_key_file = zulipconf('postgresql', 'ssl_key_file', undef)
|
||||
$ssl_ca_file = zulipconf('postgresql', 'ssl_ca_file', undef)
|
||||
$ssl_mode = zulipconf('postgresql', 'ssl_mode', undef)
|
||||
|
||||
file { $zulip::postgresql_base::postgresql_confdirs:
|
||||
ensure => directory,
|
||||
owner => 'postgres',
|
||||
group => 'postgres',
|
||||
}
|
||||
|
||||
if $version in ['14'] {
|
||||
$postgresql_conf_file = "${zulip::postgresql_base::postgresql_confdir}/postgresql.conf"
|
||||
file { $postgresql_conf_file:
|
||||
ensure => file,
|
||||
require => Package[$zulip::postgresql_base::postgresql],
|
||||
owner => 'postgres',
|
||||
group => 'postgres',
|
||||
mode => '0644',
|
||||
content => template("zulip/postgresql/${version}/postgresql.conf.template.erb"),
|
||||
}
|
||||
} elsif $version in ['15', '16', '17', '18'] {
|
||||
$postgresql_conf_file = "${zulip::postgresql_base::postgresql_confdir}/conf.d/zulip.conf"
|
||||
file { $postgresql_conf_file:
|
||||
ensure => file,
|
||||
require => Package[$zulip::postgresql_base::postgresql],
|
||||
owner => 'postgres',
|
||||
group => 'postgres',
|
||||
mode => '0644',
|
||||
content => template('zulip/postgresql/zulip.conf.template.erb'),
|
||||
}
|
||||
} else {
|
||||
fail("PostgreSQL ${version} not supported")
|
||||
}
|
||||
|
||||
# PostgreSQL 18's io_method = io_uring requires a larger
|
||||
# RLIMIT_MEMLOCK than systemd's default on Linux 6.14 and newer
|
||||
if $facts['os']['family'] == 'Debian' {
|
||||
file { '/etc/systemd/system/postgresql@.service.d':
|
||||
ensure => directory,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0755',
|
||||
}
|
||||
file { '/etc/systemd/system/postgresql@.service.d/zulip.conf':
|
||||
ensure => file,
|
||||
owner => 'root',
|
||||
group => 'root',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/postgresql/postgresql@.service.d/zulip.conf',
|
||||
notify => [Exec['reload systemd'], Service['postgresql']],
|
||||
}
|
||||
}
|
||||
|
||||
if $replication_primary != undef and $replication_user != undef {
|
||||
# The presence of a standby.signal file triggers replication
|
||||
file { "${zulip::postgresql_base::postgresql_datadir}/standby.signal":
|
||||
ensure => file,
|
||||
require => Package[$zulip::postgresql_base::postgresql],
|
||||
before => Service['postgresql'],
|
||||
owner => 'postgres',
|
||||
group => 'postgres',
|
||||
mode => '0644',
|
||||
content => '',
|
||||
}
|
||||
}
|
||||
|
||||
$backups_s3_bucket = zulipsecret('secrets', 's3_backups_bucket', '')
|
||||
$backups_directory = zulipconf('postgresql', 'backups_directory', '')
|
||||
if $backups_s3_bucket != '' or $backups_directory != '' {
|
||||
$require = [File['/usr/local/bin/env-wal-g'], Package[$zulip::postgresql_base::postgresql]]
|
||||
} else {
|
||||
$require = [Package[$zulip::postgresql_base::postgresql]]
|
||||
}
|
||||
service { 'postgresql':
|
||||
ensure => $start,
|
||||
require => $require + [Exec['reload systemd']],
|
||||
subscribe => [ File[$postgresql_conf_file] ],
|
||||
}
|
||||
}
|
||||
95
puppet/zulip/manifests/profile/rabbitmq.pp
Normal file
95
puppet/zulip/manifests/profile/rabbitmq.pp
Normal file
@@ -0,0 +1,95 @@
|
||||
class zulip::profile::rabbitmq {
|
||||
include zulip::profile::base
|
||||
$erlang = $facts['os']['family'] ? {
|
||||
'Debian' => 'erlang-base',
|
||||
'RedHat' => 'erlang',
|
||||
}
|
||||
$rabbitmq_packages = [
|
||||
$erlang,
|
||||
'rabbitmq-server',
|
||||
]
|
||||
|
||||
group { 'rabbitmq':
|
||||
ensure => present,
|
||||
system => true,
|
||||
}
|
||||
user { 'rabbitmq':
|
||||
ensure => present,
|
||||
comment => 'RabbitMQ messaging server',
|
||||
gid => 'rabbitmq',
|
||||
home => '/var/lib/rabbitmq',
|
||||
shell => '/usr/sbin/nologin',
|
||||
system => true,
|
||||
require => Group['rabbitmq'],
|
||||
}
|
||||
file { '/etc/rabbitmq':
|
||||
ensure => directory,
|
||||
owner => 'rabbitmq',
|
||||
group => 'rabbitmq',
|
||||
mode => '0755',
|
||||
require => User['rabbitmq'],
|
||||
before => Package['rabbitmq-server'],
|
||||
}
|
||||
file { '/etc/rabbitmq/rabbitmq.config':
|
||||
ensure => file,
|
||||
owner => 'rabbitmq',
|
||||
group => 'rabbitmq',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/rabbitmq/rabbitmq.config',
|
||||
# This config file must be installed before the package, so that
|
||||
# port 25672 is not even briefly open to the Internet world, which
|
||||
# would be a security risk, due to insecure defaults in the
|
||||
# RabbitMQ package.
|
||||
before => Package['rabbitmq-server'],
|
||||
notify => Service['rabbitmq-server'],
|
||||
}
|
||||
exec { 'warn-rabbitmq-nodename-change':
|
||||
command => "${facts['zulip_scripts_path']}/lib/warn-rabbitmq-nodename-change",
|
||||
onlyif => '[ -f /etc/rabbitmq/rabbitmq-env.conf ] && ! grep -xq NODENAME=zulip@localhost /etc/rabbitmq/rabbitmq-env.conf',
|
||||
before => [
|
||||
File['/etc/rabbitmq/rabbitmq-env.conf'],
|
||||
Service['rabbitmq-server'],
|
||||
],
|
||||
logoutput => true,
|
||||
loglevel => warning,
|
||||
}
|
||||
file { '/etc/rabbitmq/rabbitmq-env.conf':
|
||||
ensure => file,
|
||||
owner => 'rabbitmq',
|
||||
group => 'rabbitmq',
|
||||
mode => '0644',
|
||||
source => 'puppet:///modules/zulip/rabbitmq/rabbitmq-env.conf',
|
||||
before => Package['rabbitmq-server'],
|
||||
notify => [
|
||||
Service['rabbitmq-server'],
|
||||
Exec['configure-rabbitmq'],
|
||||
],
|
||||
}
|
||||
package { $rabbitmq_packages:
|
||||
ensure => installed,
|
||||
}
|
||||
# epmd doesn't have an init script, so we just check if it is
|
||||
# running, and if it isn't, start it. Even in case of a race, this
|
||||
# won't leak epmd processes, because epmd checks if one is already
|
||||
# running and exits if so.
|
||||
exec { 'epmd':
|
||||
command => 'epmd -daemon',
|
||||
unless => 'which pgrep && pgrep -x epmd >/dev/null',
|
||||
require => Package[$erlang],
|
||||
path => '/usr/bin/:/bin/',
|
||||
}
|
||||
|
||||
service { 'rabbitmq-server':
|
||||
ensure => running,
|
||||
require => [
|
||||
Exec['epmd'],
|
||||
Package['rabbitmq-server'],
|
||||
],
|
||||
}
|
||||
|
||||
exec { 'configure-rabbitmq':
|
||||
command => "${facts['zulip_scripts_path']}/setup/configure-rabbitmq",
|
||||
refreshonly => true,
|
||||
require => Service['rabbitmq-server'],
|
||||
}
|
||||
}
|
||||
71
puppet/zulip/manifests/profile/redis.pp
Normal file
71
puppet/zulip/manifests/profile/redis.pp
Normal file
@@ -0,0 +1,71 @@
|
||||
class zulip::profile::redis {
|
||||
include zulip::profile::base
|
||||
case $facts['os']['family'] {
|
||||
'Debian': {
|
||||
$redis = 'redis-server'
|
||||
$redis_dir = '/etc/redis'
|
||||
}
|
||||
'RedHat': {
|
||||
$redis = 'redis'
|
||||
$redis_dir = '/etc'
|
||||
}
|
||||
default: {
|
||||
fail('osfamily not supported')
|
||||
}
|
||||
}
|
||||
$redis_packages = [ # The server itself
|
||||
$redis,
|
||||
]
|
||||
|
||||
package { $redis_packages: ensure => installed }
|
||||
|
||||
$file = "${redis_dir}/redis.conf"
|
||||
$zulip_redisconf = "${redis_dir}/zulip-redis.conf"
|
||||
$line = "include ${zulip_redisconf}"
|
||||
exec { 'update-redis-conf':
|
||||
unless => "/bin/grep -Fxqe '${line}' '${file}'",
|
||||
path => '/bin',
|
||||
command => "bash -c \"(/bin/echo; /bin/echo '# Include Zulip-specific configuration'; /bin/echo '${line}') >> '${file}'\"",
|
||||
require => [
|
||||
Package[$redis],
|
||||
File[$zulip_redisconf],
|
||||
],
|
||||
}
|
||||
|
||||
$redis_password = zulipsecret('secrets', 'redis_password', '')
|
||||
file { $zulip_redisconf:
|
||||
ensure => file,
|
||||
require => Package[$redis],
|
||||
owner => 'redis',
|
||||
group => 'redis',
|
||||
mode => '0640',
|
||||
content => template('zulip/zulip-redis.template.erb'),
|
||||
}
|
||||
|
||||
# https://redis.io/docs/management/admin/#linux
|
||||
zulip::sysctl { 'redis-server':
|
||||
key => 'vm.overcommit_memory',
|
||||
value => '1',
|
||||
}
|
||||
package { 'sysfsutils': }
|
||||
file { '/etc/sysfs.d/40-disable-transpatent-hugepages.conf':
|
||||
require => Package['sysfsutils'],
|
||||
notify => Service['sysfsutils'],
|
||||
content => 'kernel/mm/transparent_hugepage/enabled = never',
|
||||
}
|
||||
service { 'sysfsutils':
|
||||
ensure => running,
|
||||
require => Package['sysfsutils'],
|
||||
}
|
||||
service { $redis:
|
||||
ensure => running,
|
||||
require => [
|
||||
Service['sysfsutils'],
|
||||
Package['redis-server'],
|
||||
],
|
||||
subscribe => [
|
||||
File[$zulip_redisconf],
|
||||
Exec['update-redis-conf'],
|
||||
],
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user