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
82 lines
2.6 KiB
Python
Executable File
82 lines
2.6 KiB
Python
Executable File
#!/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
|