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

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

View File

View File

@@ -0,0 +1,2 @@
deb [signed-by=/etc/apt/keyrings/ksplice.asc] http://www.ksplice.com/apt jammy ksplice
deb-src [signed-by=/etc/apt/keyrings/ksplice.asc] http://www.ksplice.com/apt jammy ksplice

View File

@@ -0,0 +1,65 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
5d586NuHxeqt80YNMJDN0Q==
=Y2MU
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,2 @@
deb [signed-by=/etc/apt/keyrings/ksplice.asc] http://www.ksplice.com/apt noble ksplice
deb-src [signed-by=/etc/apt/keyrings/ksplice.asc] http://www.ksplice.com/apt noble ksplice

View File

@@ -0,0 +1 @@
deb [signed-by=/etc/apt/keyrings/teleport-pubkey.asc] https://apt.releases.teleport.dev/ubuntu jammy stable/v17

View File

@@ -0,0 +1 @@
deb [signed-by=/etc/apt/keyrings/teleport-pubkey.asc] https://apt.releases.teleport.dev/ubuntu noble stable/v17

View File

@@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
oq8y1SJMCrfC9Ub5q2/ijiJWUw==
=+Ne5
-----END PGP PUBLIC KEY BLOCK-----

Binary file not shown.

View File

@@ -0,0 +1,5 @@
deb [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ bookworm-pgdg main
deb-src [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ bookworm-pgdg main
deb http://deb.debian.org/debian bookworm-backports main
deb-src http://deb.debian.org/debian bookworm-backports main

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
os_info="$(. /etc/os-release && printf '%s\n' "$ID" "$VERSION_ID" "$VERSION_CODENAME")"
{
read -r os_id
read -r os_version_id
read -r os_version_codename
} <<<"$os_info"
if [ ! -e /usr/share/doc/groonga-apt-source/copyright ] && [ "$os_id $os_version_id" != "ubuntu 26.04" ]; then
pgroonga_apt_sign_key=$(readlink -f "$LIST_PATH/pgroonga-packages.groonga.org.asc")
remove_pgroonga_apt_tmp_dir() {
rm -rf "$pgroonga_apt_tmp_dir"
}
pgroonga_apt_tmp_dir=$(mktemp --directory)
trap remove_pgroonga_apt_tmp_dir EXIT
{
cd "$pgroonga_apt_tmp_dir" || exit 1
tmp_gpg_home=.gnupg
gpg --homedir="$tmp_gpg_home" --import "$pgroonga_apt_sign_key"
# Find fingerprint of the first key.
pgroonga_apt_sign_key_fingerprint=$(
gpg --homedir="$tmp_gpg_home" --with-colons --list-keys \
| grep '^fpr:' \
| cut --delimiter=: --fields=10 \
| head --lines=1
)
groonga_apt_source_deb="groonga-apt-source-latest-$os_version_codename.deb"
groonga_apt_source_deb_sign="$groonga_apt_source_deb.asc.$pgroonga_apt_sign_key_fingerprint"
curl -fLO --retry 3 "https://packages.groonga.org/$os_id/$groonga_apt_source_deb"
curl -fLO --retry 3 "https://packages.groonga.org/$os_id/$groonga_apt_source_deb_sign"
gpg \
--homedir="$tmp_gpg_home" \
--verify \
"$groonga_apt_source_deb_sign" \
"$groonga_apt_source_deb"
# To suppress the following warning by "apt-get install":
# N: Download is performed unsandboxed as root as file
# '.../groonga-apt-source-latest-$os_version_codename.deb' couldn't be
# accessed by user '_apt'. - pkgAcquire::Run (13: Permission denied)
chown _apt .
apt-get -y install "./$groonga_apt_source_deb"
}
touch "$STAMP_FILE"
fi

View File

@@ -0,0 +1,11 @@
deb [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main
deb-src [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main
deb [signed-by=/etc/apt/keyrings/pgroonga-ppa.asc] http://ppa.launchpad.net/groonga/ppa/ubuntu jammy main
deb-src [signed-by=/etc/apt/keyrings/pgroonga-ppa.asc] http://ppa.launchpad.net/groonga/ppa/ubuntu jammy main
deb [signed-by=/etc/apt/keyrings/strukturag.asc] https://ppa.launchpadcontent.net/strukturag/libheif/ubuntu/ jammy main
deb-src [signed-by=/etc/apt/keyrings/strukturag.asc] https://ppa.launchpadcontent.net/strukturag/libheif/ubuntu/ jammy main
deb [signed-by=/etc/apt/keyrings/strukturag.asc] https://ppa.launchpadcontent.net/strukturag/libde265/ubuntu/ jammy main
deb-src [signed-by=/etc/apt/keyrings/strukturag.asc] https://ppa.launchpadcontent.net/strukturag/libde265/ubuntu/ jammy main

View File

@@ -0,0 +1,12 @@
deb [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ noble-pgdg main
deb-src [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ noble-pgdg main
deb [signed-by=/etc/apt/keyrings/pgroonga-ppa.asc] http://ppa.launchpad.net/groonga/ppa/ubuntu noble main
deb-src [signed-by=/etc/apt/keyrings/pgroonga-ppa.asc] http://ppa.launchpad.net/groonga/ppa/ubuntu noble main
deb [signed-by=/etc/apt/keyrings/strukturag.asc] https://ppa.launchpadcontent.net/strukturag/libheif/ubuntu/ noble main
deb-src [signed-by=/etc/apt/keyrings/strukturag.asc] https://ppa.launchpadcontent.net/strukturag/libheif/ubuntu/ noble main
# libde265 in 24.04 is up-to-date, so the PPA doesn't provide 24.04 packages
#deb [signed-by=/etc/apt/keyrings/strukturag.asc] https://ppa.launchpadcontent.net/strukturag/libde265/ubuntu/ noble main
#deb-src [signed-by=/etc/apt/keyrings/strukturag.asc] https://ppa.launchpadcontent.net/strukturag/libde265/ubuntu/ noble main

View File

@@ -0,0 +1,77 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=DA1T
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
6TYbUXXz7NBwM+kX8Uq9KpPXkHnP1D4TgEw7o5A=
=7PCC
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,27 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: SKS 1.1.5
Comment: Hostname: keyserver.ubuntu.com
mQINBFMyaEABEADEeUoP5AmhJZLHujySJaLKd1EXpIxkope3l4f/3tkbPTvV+jkyYvOSX/ii
NAHyVGPrg4UjJI+kDLru1glFoabBs+J4xk2Lql9DcHW0x7YPgBXKCuj2sg84Teb0KTnAkAFI
gd8w2kzs6l9itQbtCWei0wH+pGpp9YJqEqJhqc3U51LqUoMmYjTd2WTaN+BNVZOE3ws4Pa4+
JyikyVOkuj/8q+jRF8LHWmeb76SuycvZMyGjJBnIs65wmmSN6VpeHtQA3NzIAV2FKuirEEuW
5j7fD9L8WNTXy++K6NTvcd2InXgEWMG3rSGvfE2CvZSxKc/b9/xVdaG0LDtTTWPTosNdluUA
K4M91Rz7UEvC06SWIdxqL8LdLOy/6OYaJxgh1exrEadRPWAKr+Ak7LDMiYPHq80npQzMh8Hn
oUbQTaQ7KDFD17BZ7HauXn8rRmG/vpdQvKzswRoc3+mroh5hA6otjKdmOWAaD5AmNNxcvR3O
iNjDTnX804pjrjHzmTAAxrPKwPxbPzFUeNB4Dt4BzUdesdbbZYJw5ojcvt9qCfLU0DOZXWk4
t1nHvIQvGoG8cE63x1buVHcwFXwxocMUdn4iFam9srBlDx1G8TMlaKITNHvoc//evBU0arz0
12UMDb8TXAT5zOW1W6szFSt1ZFn0By9Ab+I69UCIgB2iknMUtwARAQABtBlMYXVuY2hwYWQg
UFBBIGZvciBHcm9vbmdhiQI4BBMBAgAiBQJTMmhAAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIe
AQIXgAAKCRAzWefOy2ShV5p5EACWQ5QBQV86SAhjJHfRe1MSYVZtrA3v+wwS52V/C9sV9xlc
Sn2IGIpUQM2816fVMTM23pvj9lzuP3PpcHl0vc5/gqSliRPAzXawgs8Gl+wB9FcwnKPPQfyR
CWflFg5mc37+5wM/fTagL7V5S+mBcDrAyMOr7AFgt03nTCwoSaNHatF+pBZRqpyWByM6sqD+
IF2ChI4O3QTI5H6HkbQStEHLGuz8G6UX1MBRZ9LrRl0hUqdc2F/dcogrtV5NWkimXvMcTBYB
9dTeuvj7eSma6JemqJAXfCARq54KjYwcZHapzczJe3Kgj1tSqD8O1Z8fsT16P2b0tUnDgNjX
PB9UQGsP5NLK+vGnVBg5xJA5GqaN69lsUsvPaFFngmKmykQQNlDk7Q3yrAvMJWwRPQ1c8eMq
kkrkKBal8Rq/EJaoaoBG6SFx2cSAD4GMxPEL7G3qJeNJPSLVSOP9j9nGIZ4SETeGIKCNVgVF
16iXIxwflvgJP+/1qX3/Qin2YDYCeGvFmJiy5s0VFM1IdK+znrUu0apoCSYVia1D9NJGEdKf
l7m/Odtxycq9VP1DKD/k4spWqsdpiBkJMch7XWfa7KF12bwV/tHP4KqJ02/xqhSqk+Ru4A9R
LjQFWXs4o/Qwf5qF7FlGhpFridr0xdxtLVmK7bV4Hogldx1bKbkOpCL29BY8GA==
=NjhU
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,6 @@
deb [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ resolute-pgdg main
deb-src [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ resolute-pgdg main
# Not available for Ubuntu 26.04 yet
#deb [signed-by=/etc/apt/keyrings/pgroonga-ppa.asc] http://ppa.launchpad.net/groonga/ppa/ubuntu resolute main
#deb-src [signed-by=/etc/apt/keyrings/pgroonga-ppa.asc] http://ppa.launchpad.net/groonga/ppa/ubuntu resolute main

View File

@@ -0,0 +1,28 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGY0lEwBEAD9qq6oOeRJvCi0fHNqJELvS8ykqfuXxWqn1B9DLt0VuTxgmaxG
QNaG0B2pfrhoVDs+9wwfOuW0rtUtjWpccJjZaooTIEFSR4AKZAh3O4gUtvI7y95r
q7+wWFct4RjbFyZOpmeinhIgnItTbkVjhjJvvQ66H4gUfNeJOoAE7CYgnMhZ1Sn8
E6zxWJVLo0c5hXkkc4x4T1Aq02diO0Zg6T6HesxsCLeb5Sbjrca7ukUBGV3WFKAF
QC22r8oCC4LiN5TX1WNQJjDSw0M2AY31AF+ebQvZGa1kcClWjqBCD7717+zbevPe
/bx88Yr6eh+xuJ3ZJQ209phdeNq9RRkIDnap7TIVn21VoCQolH+adP9boxgG1iIz
0gBx1KXBjRgV9owoYan4Mz9+c+5MMgJ975D+8GoRcNr1oycjOO/VVXw7ST9UiXqo
TuOU2c1P8KmM3IUF41Igy5rd7MLy9uizKC/WY/lfLOH8pzT3LoWVmhMDJHooF9fq
UC80GRIIaBM3f7sQH873zdqzaRPqtgJDxOAzLoZqUaFwLXuMfvYxhwMLWhe3b6Wp
shCe3xifqZIHf9iZkqZ9b2TsstXervs0TlW9Xy8FT8RMuEzsX8DLNsUert+PzWpq
C8Qse1AElMgSSxEsucWMbW5cnKj6ViEf10gy5VwlTgEgvt9KpZ2Icm43mwARAQAB
tB1MYXVuY2hwYWQgUFBBIGZvciBzdHJ1a3R1ciBBR4kCTgQTAQoAOBYhBEfpK5wT
yXuwgfX/wXF9tBYRYATJBQJmNJRMAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA
AAoJEHF9tBYRYATJ/oMQAPVJWmzF+UKwkCA/kFeihXdA6OWl1BttFQpwTAAmxr9u
SCg/w1ltIYqTfZcAjMUhY52oDix/NxzZZb/B4dxOkUM4gYmkbzq4wlT0QyrRgjNZ
wflwdNjXGI9Ew9ta/3xvNE98LQWyV64t+Q8PihDD7VwOE/c2XVPvXQk8GWmsHJ48
nGLaAPzn68kolv3XpkDB+ul07OLC7FXlADxWFOsobwula/zytFK2oyuQ1NiXoJ5I
3+IA5sAp7O1mDOZ3m8RROfw56IbwHlNXAP8/REbqWEsXVNkyMf56snpsIr8FPlqY
C74QbR/PHcb7vEdeWZgHmuIGNF37QPMx4XUGWjh/9NNi7AHGMa2cRsSF1xm1R99Q
Zr1vmRPJM2z54gPq/JwY7WlcL6yEFMBL+WtNuiua5xxZ3rkMs956IuFS55zjrNeO
MPmK77tdmFEui0GiTIyAJpoZIwp0AgYBdmP4/2Y6j6LN1sV+VkOzQz2CqDT87yO1
BoN65jFuw61H3Y/+OBQMtoxamxs1m/Jjv0sEYf21nKgfukOTLecRWH1tXu0moTAf
eEf0MOXzhH1teyfv/21haprwxJn0cG515oHB/JutFNKrXx3qtNH5QjRXIbz9EWAs
u1mi4ERxSWAELxiJQWztCOhB5sI8Auq+Kk4uzbCutK6BQrUQORz2FGEJwEXy1f7g
=76Dg
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,2 @@
deb [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ trixie-pgdg main
deb-src [signed-by=/etc/apt/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt/ trixie-pgdg main

View File

@@ -0,0 +1,10 @@
# libarrow1600 is missing on Debian 12
Package: libgroonga0
Pin: version 14.0.3-1
Pin-Priority: 499
# On bookworm, pull libheif1 from backports
Package: libheif1
Pin: release n=bookworm-backports
Pin-Priority: 500

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
import difflib
import os
import re
import sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)
from scripts.lib.setup_path import setup_path
setup_path()
current_path = "/etc/zulip/settings.py"
if len(sys.argv) >= 2:
current_path = sys.argv[1]
print(f"Reading current configuration from {current_path}...")
with open(current_path) as f:
current_contents = f.read()
import requests
print("Fetching old versions of setting templates from Github...")
templ = {}
url: str | None = "https://api.github.com/repos/zulip/zulip/tags?per_page=100"
while url is not None:
resp = requests.get(url)
if resp.status_code != 200:
print(resp.content)
sys.exit(1)
url = resp.links.get("next", {}).get("url")
for tag in [t["name"] for t in resp.json()]:
if not re.match(r"^\d+\.\d+(\.\d+)?$", tag):
continue
filename = "zproject/prod_settings_template.py"
if [int(x) for x in tag.split(".")] < [1, 4]:
filename = "zproject/local_settings_template.py"
print(f" - {tag}")
resp = requests.get(
f"https://raw.githubusercontent.com/zulip/zulip/{tag}/{filename}",
)
if resp.status_code == 200:
templ[tag] = resp.content.decode()
else:
print("Failure: ")
print(resp)
print("Computing minimal difference...")
sequence_matchers = {}
for version, contents in templ.items():
sm = difflib.SequenceMatcher(None, current_contents, contents)
sequence_matchers[version] = sm
likely = sorted(sequence_matchers.keys(), key=lambda v: sequence_matchers[v].ratio(), reverse=True)
print(f"Most likely version: {likely[0]}")
diff = difflib.unified_diff(
templ[likely[0]].splitlines(keepends=True),
current_contents.splitlines(keepends=True),
fromfile=f"zulip-server-{version}/zproject/prod_settings_template.py",
tofile=current_path,
)
print()
print("".join(diff), end="")

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
#
# Delete the "guest" default user and replace it with a Zulip user
# with a real password
set -eu
# If the RabbitMQ distribution cookie is insecure, reset it and
# restart RabbitMQ.
"$(dirname "$0")/generate-rabbitmq-cookie"
RABBITMQ_USERNAME=$("$(dirname "$0")/../get-django-setting" RABBITMQ_USERNAME)
RABBITMQ_PASSWORD=$("$(dirname "$0")/../get-django-setting" RABBITMQ_PASSWORD)
RABBITMQ_VHOST=$("$(dirname "$0")/../get-django-setting" RABBITMQ_VHOST)
tries=0
while ((tries < 30)) && ! rabbitmqctl ping -q 2>/dev/null; do
((tries++)) || echo "Waiting for RabbitMQ to start up..."
sleep 1
done
rabbitmqctl await_startup
rabbitmqctl delete_user "$RABBITMQ_USERNAME" || true
rabbitmqctl delete_user zulip || true
rabbitmqctl delete_user guest || true
rabbitmqctl add_user "$RABBITMQ_USERNAME" "$RABBITMQ_PASSWORD"
rabbitmqctl set_user_tags "$RABBITMQ_USERNAME" administrator
if ! rabbitmqctl list_vhosts --no-table-headers --quiet | grep -qx "$RABBITMQ_VHOST"; then
rabbitmqctl add_vhost "$RABBITMQ_VHOST"
fi
rabbitmqctl set_permissions -p "$RABBITMQ_VHOST" "$RABBITMQ_USERNAME" '.*' '.*' '.*'

61
scripts/setup/create-database Executable file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -e
if [ "$EUID" -ne 0 ]; then
echo "Error: This script must be run as root" >&2
exit 1
fi
set -x
# What user should we use for connecting to the database
POSTGRES_USER="${POSTGRES_USER:-postgres}"
# What database name and username to use when connecting to the database
DATABASE_NAME=$(crudini --get /etc/zulip/zulip.conf postgresql database_name 2>/dev/null || echo zulip)
DATABASE_USER=$(crudini --get /etc/zulip/zulip.conf postgresql database_user 2>/dev/null || echo zulip)
if [ "$(su "$POSTGRES_USER" -c "cd / && psql -v ON_ERROR_STOP=1 -Atc \"SELECT 1 FROM pg_database WHERE datname='$DATABASE_NAME';\"")" = "1" ]; then
records="$(su "$POSTGRES_USER" -c "cd / && psql -v ON_ERROR_STOP=1 -Atc 'SELECT COUNT(*) FROM $DATABASE_NAME.zerver_message;' $DATABASE_USER" || echo 0)"
if [ "$records" -gt 200 ]; then
set +x
echo "WARNING: This will delete your Zulip database which currently contains $records messages."
read -p "Do you want to proceed? [y/N] " -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
set -x
fi
fi
# Shut down all services to ensure a quiescent state.
if [ -e "/home/zulip/deployments" ]; then
su zulip -c "/home/zulip/deployments/current/scripts/stop-server"
fi
# Drop any open connections to any old database.
# Send the script via stdin in case the postgres user lacks permission to read it.
su -s /usr/bin/env - -- "$POSTGRES_USER" \
bash -s - zulip zulip_base <"$(dirname "$0")/terminate-psql-sessions"
(
cd / # Make sure the current working directory is readable by postgres
su "$POSTGRES_USER" -c "psql -v ON_ERROR_STOP=1 -v dbname=$DATABASE_NAME -v dbuser=$DATABASE_USER -e"
) <"$(dirname "$0")/create-db.sql"
# Set a postgres password if the postgres username is not "zulip".
# When the username is zulip, we rely on running as the zulip system
# user for authentication via postgres' peer authentication.
if [ "$DATABASE_USER" != "zulip" ]; then
PASSWORD=$(crudini --get /etc/zulip/zulip-secrets.conf secrets postgres_password)
su "$POSTGRES_USER" -c "psql -v ON_ERROR_STOP=1 -e postgres" <<EOF
ALTER ROLE $DATABASE_USER PASSWORD '$PASSWORD';
EOF
fi
# Clear memcached to avoid contamination from previous database state
if [ -e "/home/zulip/deployments" ]; then
/home/zulip/deployments/current/scripts/setup/flush-memcached
fi
echo "Database created"

View File

@@ -0,0 +1,20 @@
\set dbuser :dbuser
SELECT CASE WHEN :'dbuser' = ':dbuser' THEN 'zulip' ELSE :'dbuser' END AS dbuser \gset
\set dbname :dbname
SELECT CASE WHEN :'dbname' = ':dbname' THEN 'zulip' ELSE :'dbname' END AS dbname \gset
\connect postgres
DROP DATABASE IF EXISTS :"dbname";
SELECT format($$BEGIN
CREATE USER %I;
EXCEPTION WHEN duplicate_object THEN
RAISE NOTICE 'user already exists';
END$$, :'dbuser') AS code \gset
DO :'code';
CREATE DATABASE :"dbname"
OWNER=:dbuser
ENCODING=UTF8
LC_COLLATE='C.UTF-8'
LC_CTYPE='C.UTF-8'
TEMPLATE=template0;
\connect :"dbname"

View File

@@ -0,0 +1,2 @@
\connect zulip
CREATE EXTENSION pgroonga;

28
scripts/setup/flush-memcached Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
import os
import sys
BASE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..")
sys.path.append(BASE_DIR)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "zproject.settings")
from scripts.lib.setup_path import setup_path
setup_path()
import bmemcached
from django.conf import settings
cache = settings.CACHES["default"]
match cache["BACKEND"]:
case "django.core.cache.backends.locmem.LocMemCache":
pass
case "zerver.lib.singleton_bmemcached.SingletonBMemcached":
assert isinstance(cache["OPTIONS"], dict)
client = bmemcached.Client((cache["LOCATION"],), **cache["OPTIONS"])
try:
client.flush_all()
finally:
client.disconnect_all()
case _:
raise ValueError(f"Unrecognized cache backend {cache['BACKEND']}")

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
#
# rabbitmq sets an insecure "magic cookie" which is used for auth;
# reset it to be longer.
set -eu
cookiefile=/var/lib/rabbitmq/.erlang.cookie
# If the RabbitMQ distribution cookie is insecure, reset it
if [ ! -f "$cookiefile" ] || ! size="$(wc -c "$cookiefile")" || [ "${size%% *}" -le 20 ]; then
running=0
if service rabbitmq-server status >/dev/null; then
running=1
service rabbitmq-server stop
fi
echo "Setting a more secure RabbitMQ distribution magic cookie"
cookie="$(LC_ALL=C tr -dc '[:alnum:]' </dev/urandom | head -c255)"
[ "${#cookie}" -eq 255 ] # make sure tr wasn’t OOM-killed
tmpfile="$(mktemp "$cookiefile.XXXXXXXXXX")"
chown rabbitmq: "$tmpfile"
printf '%s' "$cookie" >"$tmpfile"
mv "$tmpfile" "$cookiefile"
if [ "$running" == "1" ]; then
service rabbitmq-server start
fi
fi

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env bash
set -e
usage() {
echo "usage: $0 [--force] [--exists-ok] EXTERNAL_HOST" >&2
exit 1
}
args="$(getopt -o '' --long help,force,exists-ok -- "$@")"
eval "set -- $args"
while true; do
case "$1" in
--help) usage ;;
--force)
FORCE=1
shift
;;
--exists-ok)
EXISTS_OK=1
shift
;;
--)
shift
break
;;
*) usage ;;
esac
done
EXTERNAL_HOST="$1"
if [ -z "$EXTERNAL_HOST" ]; then
usage
fi
if [ "$EUID" -ne 0 ]; then
echo "error: this script must be run as root" >&2
exit 1
fi
set -x
is_redhat=false
if [ -e /etc/redhat-release ]; then
is_redhat=true
SSLDIR=/etc/pki/tls
else
SSLDIR=/etc/ssl
fi
KEYFILE=$SSLDIR/private/zulip.key
CERTFILE=$SSLDIR/certs/zulip.combined-chain.crt
if [ -n "$EXISTS_OK" ] && [ -e "$KEYFILE" ] && [ -e "$CERTFILE" ]; then
exit 0
fi
if [ -z "$FORCE" ] && { [ -e "$KEYFILE" ] || [ -e "$CERTFILE" ]; }; then
echo "$0: certificate and/or key already exists; use --force to overwrite." >&2
exit 1
fi
rm -f "$KEYFILE" "$CERTFILE"
if [[ "$EXTERNAL_HOST" =~ ^(([0-9]+\.){3}[0-9]+)(:[0-9]+)?$ ]]; then
subjectAltName="IP:${BASH_REMATCH[1]}" # IPv4 address
elif [[ "$EXTERNAL_HOST" =~ ^\[([^][]*)\](:[0-9]+)?$ ]]; then
subjectAltName="IP:${BASH_REMATCH[1]}" # IPv6 address
elif [[ "$EXTERNAL_HOST" =~ ^([^:]+)(:[0-9]+)?$ ]]; then
subjectAltName="DNS:${BASH_REMATCH[1]}"
else
echo "$0: invalid host $EXTERNAL_HOST" >&2
exit 1
fi
config="$(mktemp)" || exit 1
trap 'rm -f "$config"' EXIT
cat >"$config" <<EOF
# Based on /usr/share/ssl-cert/ssleay.cnf from Debian's \`ssl-cert\`
# package, which is used for the system's snakeoil cert in /etc/ssl/.
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
prompt = no
policy = policy_anything
req_extensions = v3_req
x509_extensions = v3_req
[ req_distinguished_name ]
commonName = $EXTERNAL_HOST
[ v3_req ]
basicConstraints = CA:FALSE
subjectAltName = $subjectAltName
EOF
if [ "$is_redhat" = true ]; then
yum install -y openssl
else
apt-get install -y openssl
fi
# Based on /usr/sbin/make-ssl-cert from Debian's `ssl-cert` package.
openssl req -new -x509 \
-config "$config" -days 3650 -nodes -sha256 \
-out "$CERTFILE" -keyout "$KEYFILE"
chmod 644 "$CERTFILE"
chmod 640 "$KEYFILE"

228
scripts/setup/generate_secrets.py Executable file
View File

@@ -0,0 +1,228 @@
#!/usr/bin/env python3
# This tools generates /etc/zulip/zulip-secrets.conf
import json
import os
import sys
from contextlib import suppress
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)
from scripts.lib.setup_path import setup_path
from scripts.lib.zulip_tools import get_config, get_config_file
setup_path()
os.environ["DISABLE_MANDATORY_SECRET_CHECK"] = "True"
os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.settings"
import argparse
import configparser
import uuid
from nacl.encoding import Base64Encoder
from nacl.public import PrivateKey
os.chdir(os.path.join(os.path.dirname(__file__), "..", ".."))
# Standard, 64-bit tokens
AUTOGENERATED_SETTINGS = [
"avatar_salt",
"rabbitmq_password",
"shared_secret",
]
def random_string(cnt: int) -> str:
# We do in-function imports so that we only do the expensive work
# of importing cryptography modules when necessary.
#
# This helps optimize noop provision performance.
from django.utils.crypto import get_random_string
return get_random_string(cnt)
def random_token() -> str:
# We do in-function imports so that we only do the expensive work
# of importing cryptography modules when necessary.
#
# This helps optimize noop provision performance.
import secrets
return secrets.token_hex(32)
def generate_django_secretkey() -> str:
"""Secret key generation taken from Django's startproject.py"""
# We do in-function imports so that we only do the expensive work
# of importing cryptography modules when necessary.
#
# This helps optimize noop provision performance.
from django.utils.crypto import get_random_string
chars = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)"
return get_random_string(50, chars)
def get_old_conf(output_filename: str) -> dict[str, str]:
if not os.path.exists(output_filename) or os.path.getsize(output_filename) == 0:
return {}
secrets_file = configparser.RawConfigParser()
secrets_file.read(output_filename)
return dict(secrets_file.items("secrets"))
def generate_secrets(development: bool = False) -> None:
if development:
OUTPUT_SETTINGS_FILENAME = "zproject/dev-secrets.conf"
else:
OUTPUT_SETTINGS_FILENAME = "/etc/zulip/zulip-secrets.conf"
current_conf = get_old_conf(OUTPUT_SETTINGS_FILENAME)
lines: list[str] = []
if len(current_conf) == 0:
lines = ["[secrets]\n"]
def need_secret(name: str) -> bool:
return name not in current_conf
def add_secret(name: str, value: str) -> None:
lines.append(f"{name} = {value}\n")
current_conf[name] = value
for name in AUTOGENERATED_SETTINGS:
if need_secret(name):
add_secret(name, random_token())
# These secrets are exclusive to a Zulip development environment.
# We use PostgreSQL peer authentication by default in production,
# and initial_password_salt is used to generate passwords for the
# test/development database users. See `manage.py
# print_initial_password`.
if development and need_secret("initial_password_salt"):
add_secret("initial_password_salt", random_token())
if development and need_secret("local_database_password"):
add_secret("local_database_password", random_token())
# We only need a secret if the database username does not match
# the OS username, as identd auth works in that case.
if get_config(
get_config_file(), "postgresql", "database_user", "zulip"
) != "zulip" and need_secret("postgres_password"):
add_secret("postgres_password", random_token())
# The core Django SECRET_KEY setting, used by Django internally to
# secure sessions. If this gets changed, all users will be logged out.
if need_secret("secret_key"):
secret_key = generate_django_secretkey()
add_secret("secret_key", secret_key)
# To prevent Django ImproperlyConfigured error
from zproject import settings
settings.SECRET_KEY = secret_key
# Secret key for the Camo HTTPS proxy.
if need_secret("camo_key"):
add_secret("camo_key", random_string(64))
# We enable Altcha in development
if development and need_secret("altcha_hmac"):
add_secret("altcha_hmac", random_token())
if not development:
# The memcached_password and redis_password secrets are only
# required/relevant in production.
# Password for authentication to memcached.
if need_secret("memcached_password"):
# We defer importing settings unless we need it, because
# importing settings is expensive (mostly because of
# django-auth-ldap) and we want the noop case to be fast.
from zproject import settings
if settings.MEMCACHED_LOCATION == "127.0.0.1:11211":
add_secret("memcached_password", random_token())
# Password for authentication to Redis.
if need_secret("redis_password"):
# We defer importing settings unless we need it, because
# importing settings is expensive (mostly because of
# django-auth-ldap) and we want the noop case to be fast.
from zproject import settings
if settings.REDIS_HOST == "127.0.0.1":
# To prevent Puppet from restarting Redis, which would lose
# data because we configured Redis to disable persistence, set
# the Redis password on the running server and edit the config
# file directly.
import redis
from zerver.lib.redis_utils import get_redis_client
redis_password = random_token()
for filename in ["/etc/redis/zuli-redis.conf", "/etc/redis/zulip-redis.conf"]:
if os.path.exists(filename):
with open(filename, "a") as f:
f.write(
"# Set a Redis password based on zulip-secrets.conf\n"
f"requirepass '{redis_password}'\n",
)
break
with suppress(redis.exceptions.ConnectionError):
get_redis_client().config_set("requirepass", redis_password)
add_secret("redis_password", redis_password)
# Random id and secret used to identify this installation when
# accessing the Zulip mobile push notifications service.
# * zulip_org_key is generated using os.urandom().
# * zulip_org_id only needs to be unique, so we use a UUID.
if need_secret("zulip_org_key"):
add_secret("zulip_org_key", random_string(64))
if need_secret("zulip_org_id"):
add_secret("zulip_org_id", str(uuid.uuid4()))
if development and need_secret("push_registration_encryption_keys"):
# 'settings.ZILENCER_ENABLED' would be a better check than
# 'development' for whether we need push bouncer secrets,
# but we're trying to avoid importing settings.
private_key = PrivateKey.generate()
private_key_str = Base64Encoder.encode(bytes(private_key)).decode("utf-8")
public_key_str = Base64Encoder.encode(bytes(private_key.public_key)).decode("utf-8")
add_secret(
"push_registration_encryption_keys", json.dumps({public_key_str: private_key_str})
)
if len(lines) == 0:
print("generate_secrets: No new secrets to generate.")
return
with open(OUTPUT_SETTINGS_FILENAME, "a") as f:
# Write a newline at the start, in case there was no newline at
# the end of the file due to human editing.
f.write("\n" + "".join(lines))
print(f"Generated new secrets in {OUTPUT_SETTINGS_FILENAME}.")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"--development", action="store_true", help="For setting up the developer env for zulip"
)
group.add_argument(
"--production",
action="store_false",
dest="development",
help="For setting up the production env for zulip",
)
results = parser.parse_args()
generate_secrets(results.development)

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -e
usage() {
echo "usage: initialize-database [--quiet]" >&2
exit 1
}
args="$(getopt -o '' --long help,quiet -- "$@")"
eval "set -- $args"
while true; do
case "$1" in
--help) usage ;;
--quiet)
QUIET=1
shift
;;
--)
shift
break
;;
*) usage ;;
esac
done
if [ "$#" -gt 0 ]; then
usage
fi
set -x
# Change to root directory of the checkout that we're running from
THIS_DIR="$(dirname "$(readlink -f "$0")")"
cd "$THIS_DIR/../.."
./manage.py migrate --noinput
./manage.py createcachetable third_party_api_results
# Check if the supervisor socket exists. If not, it could be:
#
# A) A normal installation went bad (supervisor hasn't started)
# B) We are in a Docker container and don't have supervisor running
#
# In either case, it doesn't make sense to restart supervisor jobs
if [ -e "/var/run/supervisor.sock" ]; then
./scripts/restart-server
fi
set +x
if [ -z "$QUIET" ]; then
echo "Congratulations! You have successfully configured your Zulip database."
echo "If you haven't already, you should configure email in /etc/zulip/settings.py."
echo
echo "Next, run as the zulip user (use 'su zulip' if needed):"
echo
echo " /home/zulip/deployments/current/manage.py generate_realm_creation_link"
echo
echo "This generates a secure, single-use link that you can use to set up "
echo "a Zulip organization from the convenience of your web browser."
fi

27
scripts/setup/install Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
#
# Thin wrapper around the actual install script (scripts/lib/install).
# The purpose of this wrapper is to log the full install script output
# to /var/log/zulip/install.log for easy debugging.
set -e
if [ "$EUID" -ne 0 ]; then
echo "Error: The installation script must be run as root." >&2
exit 1
fi
umask 022
mkdir -p /var/log/zulip
"$(dirname "$(dirname "$0")")/lib/install" "$@" 2>&1 | tee -a /var/log/zulip/install.log
failed=${PIPESTATUS[0]}
if [ "$failed" -ne 0 ]; then
echo -e '\033[0;31m'
echo "Zulip installation failed (exit code $failed)!"
echo
echo -n "The install process is designed to be idempotent, so you can retry "
echo -n "after resolving whatever issue caused the failure (there should be a traceback above). "
echo -n "A log of this installation is available in /var/log/zulip/install.log"
echo -e '\033[0m'
exit "$failed"
fi

25
scripts/setup/pgroonga-config Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -eux
dbversion=$(crudini --get /etc/zulip/zulip.conf postgresql version)
dbname=$(crudini --get /etc/zulip/zulip.conf postgresql database_name 2>/dev/null || echo zulip)
if ! su postgres -c "psql -At -c 'SELECT datname FROM pg_database WHERE NOT datistemplate'" | grep -Fx "$dbname"; then
echo "No database to install into!"
exit 0
fi
sharedir="${1:-/usr/share/postgresql/$dbversion}"
applied_file="$sharedir/pgroonga_setup.sql.applied"
installed_version=$(dpkg-query --show --showformat='${Version}' "postgresql-$dbversion-pgdg-pgroonga")
if [ ! -f "$applied_file" ]; then
sql="CREATE EXTENSION PGROONGA"
else
sql="ALTER EXTENSION pgroonga UPDATE"
fi
echo "$sql" | su postgres -c "psql -v ON_ERROR_STOP=1 $dbname"
echo "$installed_version" >"$applied_file"

View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python3
import argparse
import logging
import os
import sys
import time
sys.path.append(os.path.join(os.path.dirname(__file__), "../.."))
from scripts.lib.setup_path import setup_path
setup_path()
import psycopg2
import psycopg2.extensions
from psycopg2.sql import SQL, Identifier
from scripts.lib.zulip_tools import su_to_zulip
su_to_zulip()
os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.settings"
from django.conf import settings
parser = argparse.ArgumentParser(
description="Reindex all text indexes, for glibc upgrades. This will take a write lock on every table, unless --concurrently is passed."
)
parser.add_argument(
"--concurrently", action="store_true", help="reindex concurrently, on Pg ≥ 11; takes longer"
)
parser.add_argument("--force", action="store_true", help="run the reindexing")
options = parser.parse_args()
logging.Formatter.converter = time.gmtime
logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s")
logger = logging.getLogger("reindex-textual-data")
logger.setLevel(logging.DEBUG)
pg_args = {}
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"
# connection_factory=None lets mypy understand the return type
conn = psycopg2.connect(connection_factory=None, **pg_args)
conn.autocommit = True
cursor = conn.cursor()
cursor.execute(
"""
SELECT
irel.relname AS index_name,
trel.relname AS table_name,
pg_size_pretty(pg_table_size(i.indrelid)) AS size
FROM
pg_index AS i
JOIN pg_class AS trel ON trel.oid = i.indrelid
JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
JOIN pg_class AS irel ON irel.oid = i.indexrelid
JOIN pg_attribute AS a ON a.attrelid = i.indexrelid
WHERE tnsp.nspname = 'zulip'
AND a.attcollation != 0
GROUP BY 1, 2, i.indrelid
ORDER BY pg_table_size(i.indrelid) ASC
"""
)
reindex_targets = cursor.fetchall()
if not options.force:
print("Would reindex the following indexes:")
for index, _, _ in reindex_targets:
print(f" {index}")
print(
"""
Re-run with --force to reindex these. Without --concurrently, these
index rebuilds will TAKE A WRITE LOCK on their respective tables,
which means that production traffic will be affected.
On PostgreSQL 11 and above, you can pass --concurrently, which will do
the index rebuilds without taking such a lock. We recommend only using
--concurrently if the Zulip server will be serving traffic while
running this tool, because concurrent reindexing takes longer to
complete even if the server is not running.
"""
)
sys.exit(0)
had_failures = False
for index_name, table_name, index_size in reindex_targets:
if options.concurrently:
command = SQL("REINDEX INDEX CONCURRENTLY {}").format(Identifier(index_name))
else:
command = SQL("REINDEX INDEX {}").format(Identifier(index_name))
logger.info("%s -- on %s, %s", command, table_name, index_size)
try:
cursor.execute(command)
except psycopg2.OperationalError as e:
logger.warning("Failed to reindex %s: %s", index_name, e)
had_failures = True
if not had_failures:
sys.exit(0)
print(
"""
=========================> REINDEXING FAILED <=========================
Reindexing failed on one or more indexes; this is generally caused
by duplicate rows that had been incorrectly inserted because of
corrupted database indexes. This happens, for example, when glibc was
upgraded to 2.28 or higher, which has a different ordering of text
("collation") than earlier versions. Because of the new ordering,
entries in the indexes are not found (because they exist in a
different place), which results in duplicates being created in the
database for columns which should be unique.
There is no generalized tool to fix these duplicate rows, but in most cases
the database can be repaired with some manual work. We are using
this chat.zulip.org for support around such repairs:
https://chat.zulip.org/#narrow/channel/31-production-help/topic/database.20corruption
This tool has regenerated all database indexes that do not have duplicate rows.
Once you have manually repaired duplicate rows, you can rerun this command
to regenerate the rest.
It is reasonable to run your Zulip server to avoid downtime while you
plan the manual repair work. Additional duplicate rows may be created
for any corrupted index that has not yet been regenerated. But any new
duplicate rows will be similar to existing duplicate rows that you
already need to manually repair.
"""
)
sys.exit(1)

187
scripts/setup/restore-backup Executable file
View File

@@ -0,0 +1,187 @@
#!/usr/bin/env python3
import argparse
import os
import re
import subprocess
import sys
import tempfile
from typing import IO
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)
from scripts.lib.zulip_tools import assert_running_as_root, get_postgres_pwent, run, su_to_zulip
POSTGRES_PWENT = get_postgres_pwent()
parser = argparse.ArgumentParser()
parser.add_argument("tarball", help="Filename of input tarball")
parser.add_argument(
"--keep-settings", help="Do not overwrite local /etc/zulip/settings.py", action="store_true"
)
parser.add_argument(
"--keep-zulipconf", help="Do not overwrite local /etc/zulip/zulip.conf", action="store_true"
)
def restore_backup(tarball_file: IO[bytes], keep_settings: bool, keep_zulipconf: bool) -> None:
assert_running_as_root()
su_to_zulip(save_suid=True)
from scripts.lib.setup_path import setup_path
setup_path()
# First, we unpack the /etc/zulip configuration, so we know how
# this server is supposed to be configured (and can import
# /etc/zulip/settings.py via `from django.conf import settings`,
# next). Ignore errors if zulip-backup/settings is not present
# (E.g. because this is a development backup).
tarball_file.seek(0, 0)
excludes = []
if keep_settings:
excludes += ["--exclude=settings.py"]
if keep_zulipconf:
excludes += ["--exclude=zulip.conf"]
run(
[
"tar",
"--directory=/etc/zulip",
"--keep-directory-symlink",
*excludes,
"--strip-components=2",
"-xz",
"zulip-backup/settings",
],
stdin=tarball_file,
)
os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.settings"
from django.conf import settings
paths = [
# zproject will only be present for development environment backups.
("zproject", os.path.join(settings.DEPLOY_ROOT, "zproject")),
]
if settings.LOCAL_UPLOADS_DIR is not None:
# We only need to restore LOCAL_UPLOADS_DIR if the system is
# configured to locally host uploads.
paths.append(("uploads", os.path.join(settings.DEPLOY_ROOT, settings.LOCAL_UPLOADS_DIR)))
with tempfile.TemporaryDirectory(prefix="zulip-restore-backup-") as tmp:
uid = os.getuid()
gid = os.getgid()
os.setresuid(0, 0, 0)
for name, path in paths:
os.makedirs(path, exist_ok=True)
os.chown(path, uid, gid)
os.setresuid(uid, uid, 0)
assert not any("|" in name or "|" in path for name, path in paths)
transform_args = [
r"--transform=s|^zulip-backup/{}(/.*)?$|{}\1|x".format(
re.escape(name),
path.replace("\\", r"\\"),
)
for name, path in paths
]
os.mkdir(os.path.join(tmp, "zulip-backup"))
tarball_file.seek(0, 0)
run(
["tar", "-C", tmp, "--keep-directory-symlink", *transform_args, "-xPz"],
stdin=tarball_file,
)
# Now, extract the database backup, destroy the old
# database, and create a new, empty database.
db = settings.DATABASES["default"]
assert isinstance(db["NAME"], str)
db_dir = os.path.join(tmp, "zulip-backup", "database")
os.setresuid(0, 0, 0)
run(["chown", "-R", POSTGRES_PWENT.pw_name, "--", tmp])
# Open the terminate-psql-sessions script while still running
# as root, since the postgres user may lack permission to read
# it -- e.g. because /home/zulip is not world-readable, which
# is the default for home directories on Debian 13 and Ubuntu
# 21.04 and newer.
terminate_psql_sessions_fd = os.open(
os.path.join(settings.DEPLOY_ROOT, "scripts", "setup", "terminate-psql-sessions"),
os.O_RDONLY,
)
os.setresuid(POSTGRES_PWENT.pw_uid, POSTGRES_PWENT.pw_uid, 0)
postgresql_env = dict(os.environ)
if db["HOST"] not in ["", "localhost"]:
postgresql_env["PGHOST"] = db["HOST"]
if "PORT" in db:
postgresql_env["PGPORT"] = str(db["PORT"])
postgresql_env["PGUSER"] = db["USER"]
if "PASSWORD" in db:
postgresql_env["PGPASSWORD"] = db["PASSWORD"]
run(
["bash", "-s", "-", db["NAME"]],
stdin=terminate_psql_sessions_fd,
env=postgresql_env,
)
os.close(terminate_psql_sessions_fd)
run(["dropdb", "--if-exists", "--", db["NAME"]], cwd="/", env=postgresql_env)
run(
["createdb", "--owner=zulip", "--template=template0", "--", db["NAME"]],
cwd="/",
env=postgresql_env,
)
os.setresuid(0, 0, 0)
if settings.PRODUCTION:
# In case we are restoring a backup from an older Zulip
# version, there may be new secrets to generate.
run(
[
os.path.join(settings.DEPLOY_ROOT, "scripts", "setup", "generate_secrets.py"),
"--production",
]
)
# If there is a local RabbitMQ, we need to reconfigure it
# to ensure the RabbitMQ password matches the value in the
# restored zulip-secrets.conf. We need to be careful to
# only do this if RabbitMQ is configured to run locally on
# the system.
rabbitmq_host = subprocess.check_output(
[
os.path.join(settings.DEPLOY_ROOT, "scripts", "get-django-setting"),
"RABBITMQ_HOST",
],
text=True,
).strip()
if rabbitmq_host in ["127.0.0.1", "::1", "localhost", "localhost6"]:
run([os.path.join(settings.DEPLOY_ROOT, "scripts", "setup", "configure-rabbitmq")])
# In production, we also need to do a `zulip-puppet-apply`
# in order to apply any configuration from
# /etc/zulip/zulip.conf to this system, since it was
# originally installed without the restored copy of that
# file.
run([os.path.join(settings.DEPLOY_ROOT, "scripts", "zulip-puppet-apply"), "-f"])
# Now, restore the database backup using pg_restore. This
# needs to run after zulip-puppet-apply to ensure full-text
# search extensions are available and installed.
os.setresuid(POSTGRES_PWENT.pw_uid, POSTGRES_PWENT.pw_uid, 0)
run(["pg_restore", "--dbname=" + db["NAME"], "--", db_dir], cwd="/", env=postgresql_env)
os.setresuid(0, 0, 0)
run(["chown", "-R", str(uid), "--", tmp])
os.setresuid(uid, uid, 0)
if settings.PRODUCTION:
run([os.path.join(settings.DEPLOY_ROOT, "scripts", "restart-server")])
run([os.path.join(settings.DEPLOY_ROOT, "scripts", "setup", "flush-memcached")])
if __name__ == "__main__":
args = parser.parse_args()
with open(args.tarball, "rb") as tarball_file:
restore_backup(tarball_file, args.keep_settings, args.keep_zulipconf)

112
scripts/setup/setup-certbot Executable file
View File

@@ -0,0 +1,112 @@
#!/usr/bin/env bash
set -e
usage() {
cat <<EOF >&2
Usage: $0 --email=admin@example.com [--method={webroot|standalone}] \
hostname.example.com [another.example.com]
EOF
exit 1
}
if [ "$EUID" -ne 0 ]; then
echo "Error: This script must be run as root" >&2
exit 1
fi
method=webroot
args="$(getopt -o '' --long help,email:,method:,agree-tos -n "$0" -- "$@")"
eval "set -- $args"
agree_tos=()
while true; do
case "$1" in
--email)
EMAIL="$2"
shift
shift
;;
--method)
method="$2"
shift
shift
;;
--agree-tos)
agree_tos=(--agree-tos)
shift
;;
--help)
show_help=1
shift
;;
--)
shift
break
;;
esac
done
# Parse the remaining arguments as Subject Alternative Names to pass to certbot
HOSTNAMES=()
for arg; do
HOSTNAMES+=(-d "$arg")
done
DOMAIN=$1
if [ -n "$show_help" ]; then
usage
fi
if [ -z "$DOMAIN" ] || [ -z "$EMAIL" ]; then
usage
fi
case "$method" in
standalone)
method_args=(--standalone --no-directory-hooks)
;;
webroot)
method_args=(--webroot '--webroot-path=/var/lib/zulip/certbot-webroot/')
;;
*)
usage
;;
esac
# If we aren't being run interactively, default to keeping the
# existing certificate (rather than burning through a renewal)
# If run interactively, certbot will prompt.
default_keep=()
if [ ! -t 0 ]; then
default_keep=(--keep-until-expiring)
fi
# We need to know _which_ domain is Zulip's, in the symlink deploy
# hook, so we pass this down
export ZULIP_DOMAIN="$DOMAIN"
# Certbot does not run deploy hooks on new certificates
# (certbot/certbot#9978) so we will need to fake it if so
if [ -d "/etc/letsencrypt/live/$DOMAIN/" ]; then
needs_hooks=0
else
needs_hooks=1
fi
certbot certonly "${method_args[@]}" \
"${HOSTNAMES[@]}" -m "$EMAIL" \
"${agree_tos[@]}" \
"${default_keep[@]}" \
--no-eff-email
# "certbot certonly" before version 3.2.0 does not run deploy hooks,
# so we fake running them.
if [ "$needs_hooks" = "1" ]; then
export RENEWED_DOMAINS="$*"
if [ "$method" == "webroot" ]; then
for deploy_hook in /etc/letsencrypt/renewal-hooks/deploy/*; do
"$deploy_hook"
done
fi
fi
echo "Certbot SSL certificate configuration succeeded."

32
scripts/setup/sha256-file-to Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/sh
if [ "$#" -ne 3 ]; then
echo "Usage:"
echo " sha256-file-to SHA256 http://FETCH/FROM DST"
echo
echo "SHA256 is the sha256sum of the file fetched; the file is"
echo "placed in DST."
exit 1
fi
set -e
set -x
SHA256="$1"
URL="$2"
DST="$3"
# Work in a tmpdir which we clean up at the end
tmpdir="$(mktemp -d)"
trap 'rm -r "$tmpdir"' EXIT
cd "$tmpdir"
# Fetch to a predictable name, not whatever curl guesses from the URL
LOCALFILE="output"
curl -fL --retry 3 -o "$LOCALFILE" "$URL"
# Check the hash against what was passed in
echo "$SHA256 $LOCALFILE" >"$LOCALFILE.sha256"
sha256sum -c "$LOCALFILE.sha256"
mv "$LOCALFILE" "$DST"

56
scripts/setup/sha256-tarball-to Executable file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
if [ "$#" -lt 4 ]; then
echo "Usage:"
echo " sha256-tarball-to SHA256 http://FETCH/FROM.tar.gz SRC1 DST1 [SRC2 DST2 [...]]"
echo
echo "SHA256 is the sha256sum of the tarball fetched; each SRC (which may be a"
echo "directory) is expected to be a relative path into the unpacked tarball,"
echo "and each DST is the absolute path it should be moved to."
exit 1
fi
set -e
set -x
SHA256="$1"
URL="$2"
shift
shift
# Work in a tmpdir which we clean up at the end
tmpdir="$(mktemp -d)"
trap 'rm -r "$tmpdir"' EXIT
cd "$tmpdir"
# We support both .tar.gz and .zip files
if [[ "$URL" == *.tar.gz ]] || [[ "$URL" == *.tgz ]]; then
extension="tar.gz"
elif [[ "$URL" == *.zip ]]; then
extension="zip"
else
echo "Can't determine archive type from URL: $URL"
exit 1
fi
# Fetch to a predictable name, not whatever curl guesses from the URL
LOCALFILE="archive.$extension"
curl -fL --retry 3 -o "$LOCALFILE" "$URL"
# Check the hash against what was passed in
echo "$SHA256 $LOCALFILE" >"$LOCALFILE.sha256"
sha256sum -c "$LOCALFILE.sha256"
if [[ "$extension" == "tar.gz" ]]; then
tar xzf "$LOCALFILE"
else
unzip "$LOCALFILE"
fi
# Take the rest of the arguments two-at-a-time, as source and
# destination to move out of the unpacked tarball.
while [ "$#" -gt 0 ]; do
mv "$1" "$2"
shift
shift
done

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -e
cd /
tables="$(printf "'%s'," "${@//\'/\'\'}")"
tables="${tables%,}"
psql postgres -v ON_ERROR_STOP=1 <<EOF
SELECT pg_terminate_backend(s.pid)
FROM pg_stat_activity s, pg_roles r
WHERE
s.datname IN ($tables)
AND r.rolname = CURRENT_USER
AND (s.usename = r.rolname OR r.rolsuper = 't')
AND s.pid <> pg_backend_pid();
EOF

149
scripts/setup/upgrade-postgresql Executable file
View File

@@ -0,0 +1,149 @@
#!/usr/bin/env bash
set -euo pipefail
if [ "$EUID" -ne 0 ]; then
echo "Error: This script must be run as root" >&2
exit 1
fi
# Force a known locale; some OS X terminals set an invalid LC_CTYPE
# which crashes pg_upgradecluster
export LC_ALL=C.UTF-8
export LANG=C.UTF-8
export LANGUAGE=C.UTF-8
LATEST_SUPPORTED_VERSION=18
UPGRADE_TO=${1:-$LATEST_SUPPORTED_VERSION}
UPGRADE_FROM=$(crudini --get /etc/zulip/zulip.conf postgresql version)
ZULIP_PATH="$(dirname "$0")/../.."
if [ "$UPGRADE_TO" = "$UPGRADE_FROM" ]; then
echo "Already running PostgreSQL $UPGRADE_TO!"
exit 1
fi
if [[ "$UPGRADE_TO" -lt "$UPGRADE_FROM" ]]; then
echo "Refusing to downgrade PostgreSQL $UPGRADE_FROM to $UPGRADE_TO!"
exit 1
fi
if [[ "$UPGRADE_TO" -gt "$LATEST_SUPPORTED_VERSION" ]]; then
echo "This version of Zulip does not support PostgreSQL $UPGRADE_TO."
echo "You may need to upgrade Zulip before you can upgrade to PostgreSQL $UPGRADE_TO."
exit 1
fi
# Verify that the version in /etc/zulip/zulip.conf is the version that
# Django actually stores its data in. We can only do that if the
# database server is on the same host as the application server.
if [ -d /home/zulip/deployments/current ]; then
DATA_IS_IN=$(
su -s /usr/bin/env -- zulip \
DJANGO_SETTINGS_MODULE=zproject.settings \
uv run --directory=/home/zulip/deployments/current --no-sync \
python -c 'from django.db import connection; print(connection.cursor().connection.server_version // 10000)'
)
if [ "$UPGRADE_FROM" != "$DATA_IS_IN" ]; then
cat <<EOF
/etc/zulip/zulip.conf claims that Zulip is running PostgreSQL
$UPGRADE_FROM, but the server is connected to a PostgreSQL running
version $DATA_IS_IN. Check the output from pg_lsclusters to verify
which clusters are running, and update /etc/zulip/zulip.conf to match.
In general, this results from manually upgrading PostgreSQL; you
should use this tool for all PostgreSQL upgrades.
EOF
exit 1
fi
fi
set -x
"$ZULIP_PATH"/scripts/lib/setup-apt-repo
apt-get install -y "postgresql-$UPGRADE_TO" "postgresql-client-$UPGRADE_TO"
drop_main_cluster="$(
pg_lsclusters --json \
| jq --arg pg_version "$UPGRADE_TO" -r \
'.[] | select((.version|tostring == $ARGS.named.pg_version) and (.cluster == "main")) | .cluster'
)"
if [ -n "$drop_main_cluster" ]; then
pg_dropcluster "$UPGRADE_TO" main --stop
fi
(
# Two-stage application of Puppet; we apply the bare-bones
# PostgreSQL configuration first, so that FTS will be configured
# prior to the pg_upgradecluster.
TEMP_CONF_DIR=$(mktemp -d)
cp /etc/zulip/zulip.conf "$TEMP_CONF_DIR"
ZULIP_CONF="${TEMP_CONF_DIR}/zulip.conf"
crudini --set "$ZULIP_CONF" postgresql version "$UPGRADE_TO"
if [ -f "/usr/share/postgresql/$UPGRADE_FROM/pgroonga_setup.sql.applied" ]; then
# This file is intentionally blank, to trigger always running
# `ALTER EXTENSION pgroonga UPDATE` and not `CREATE EXTENSION pgroonga`.
touch "/usr/share/postgresql/$UPGRADE_TO/pgroonga_setup.sql.applied"
fi
"$ZULIP_PATH"/scripts/zulip-puppet-apply -f --config "$ZULIP_CONF" --tags postgresql_upgrade
rm -rf "$TEMP_CONF_DIR"
)
# Capture the output so we know where the path to the post-upgrade scripts is
UPGRADE_LOG=$(mktemp "/var/log/zulip/upgrade-postgresql-$UPGRADE_FROM-$UPGRADE_TO.XXXXXXXXX.log")
pg_upgradecluster -v "$UPGRADE_TO" "$UPGRADE_FROM" main --method=upgrade --link --no-start | tee "$UPGRADE_LOG"
SCRIPTS_PATH=$(grep -o "/var/log/postgresql/pg_upgradecluster-$UPGRADE_FROM-$UPGRADE_TO-main.*" "$UPGRADE_LOG" || true)
# If the upgrade completed successfully, lock in the new version in
# our configuration immediately
crudini --set /etc/zulip/zulip.conf postgresql version "$UPGRADE_TO"
# Make sure the new PostgreSQL is running
pg_ctlcluster "$UPGRADE_TO" main start
# Update the statistics
su postgres -c "/usr/lib/postgresql/$UPGRADE_TO/bin/vacuumdb --all --analyze-only --jobs 10"
# Update extensions
if [ -n "$SCRIPTS_PATH" ] && [ -f "$SCRIPTS_PATH/update_extensions.sql" ]; then
su postgres -c "psql --file $SCRIPTS_PATH/update_extensions.sql"
rm "$SCRIPTS_PATH/update_extensions.sql"
fi
# Start the database up cleanly
"$ZULIP_PATH"/scripts/zulip-puppet-apply -f
# Drop the old data, binaries, and scripts
pg_dropcluster "$UPGRADE_FROM" main
apt remove -y "postgresql-$UPGRADE_FROM"
if [ -n "$SCRIPTS_PATH" ] && [ -f "$SCRIPTS_PATH/delete_old_cluster.sh" ]; then
su postgres -c "$SCRIPTS_PATH/delete_old_cluster.sh"
rm "$SCRIPTS_PATH/delete_old_cluster.sh"
fi
if [ -n "$SCRIPTS_PATH" ]; then
if [ -n "$(ls -A "$SCRIPTS_PATH")" ]; then
set +x
echo
echo
echo ">>>>> pg_upgradecluster succeeded, but unexpected post-upgrade scripts"
echo " were found:"
ls -A1 "$SCRIPTS_PATH"
else
rmdir "$SCRIPTS_PATH"
fi
else
set +x
echo
echo
echo ">>>>> pg_upgradecluster succeeded, but post-upgrade scripts path could not"
echo " be parsed out! Please read the pg_upgradecluster output to understand"
echo " the current status of your cluster:"
echo " $UPGRADE_LOG"
echo " and report this bug with the PostgreSQL $UPGRADE_FROM -> $UPGRADE_TO upgrade to:"
echo " https://github.com/zulip/zulip/issues"
echo
echo
fi