Files
thepraandClaude Opus 5.5 12bb75809f The server backs itself up: every collection byte for byte, the media linked
A backup is a directory <stamp>-<kind> under Backups:Root (/var/lib/privapub/backups, 2770, files 0640), written as
.partial and renamed once whole: a manifest (host, build, newest migration, each collection's count, size, sha256 and
indexes, what was left out and why, the media list), each collection as gzipped canonical Extended JSON read raw, and
hard links to the files of untrashed media rows (copies where a link can't be made). On a replica set every collection
is read in one snapshot session. Never in a backup: the statistics salt, jobs, recovery codes, sessions, the
maintenance lock and the configuration's copy with its SMTP password.

One backup or restore at a time (MaintenanceLock, a heartbeat document), and the janitor purges nothing meanwhile.
BackupScheduler backs up nightly at 03:30 UTC (or at once after missing a night); rotation keeps 7 daily, 4 weekly,
3 pre-deploy and 3 pre-restore backups. CLI: admin backup [--kind] [--db-only], admin backups, admin backup verify;
these run before migrations, so the deploy's own pre-deploy backup, which replaces mongodump, is of the database as the
live build left it. EntityMaps.Warm runs once under a lock, since test hosts now boot side by side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-07 11:40:06 +02:00

70 lines
3.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# One-time root setup on Max for PrivaPub at privapub.thepra.dev. Idempotent.
# rsync -a -e $MAX/ssh.sh deploy/ root@nuvola.xyz:/root/privapub-deploy/
# $MAX/run.sh bash /root/privapub-deploy/max/setup.sh
set -euo pipefail
SRC="${1:-/root/privapub-deploy}"
HOST=privapub.thepra.dev
UNIT=privapub
RUNNER=build-runner
ACME=/root/.acme.sh/acme.sh
echo "== directories"
install -d -o "$RUNNER" -g www-data -m 755 /var/www/$HOST
install -d -o "$RUNNER" -g "$RUNNER" -m 750 /var/backups/$HOST
install -d -o www-data -g www-data -m 750 /var/lib/privapub /var/lib/privapub/mongo
# backups: the service writes them, and so does the deploy (as $RUNNER, a member of www-data) before each deploy; a runner
# just added to the group gets it when the runner restarts
id -nG "$RUNNER" | grep -qw www-data || usermod -aG www-data "$RUNNER"
install -d -o www-data -g www-data -m 2770 /var/lib/privapub/backups
echo "== sudoers"
SUDOERS=/etc/sudoers.d/$RUNNER
touch "$SUDOERS"; chmod 440 "$SUDOERS"
grep -qF "systemctl start $UNIT," "$SUDOERS" || echo "$RUNNER ALL=(root) NOPASSWD: /usr/bin/systemctl start $UNIT, /usr/bin/systemctl stop $UNIT, /usr/bin/systemctl restart $UNIT, /usr/bin/systemctl is-active $UNIT, /usr/bin/systemctl show $UNIT*, /usr/bin/systemctl status $UNIT*" >> "$SUDOERS"
visudo -cf "$SUDOERS"
echo "== units"
install -m 644 "$SRC/systemd/privapub-mongod.service" /etc/systemd/system/privapub-mongod.service
install -m 644 "$SRC/systemd/$UNIT.service" /etc/systemd/system/$UNIT.service
systemctl daemon-reload
systemctl enable --now privapub-mongod >/dev/null
systemctl enable $UNIT >/dev/null
systemctl is-active privapub-mongod
echo "== replica set"
# once: mongod restarted with --replSet (PrivaPub stopped meanwhile), the one-member set rs0 made, its primary awaited
if [ "$(mongosh --quiet --port 27022 --eval 'db.hello().setName' 2>/dev/null)" != "rs0" ]; then
systemctl stop $UNIT
systemctl restart privapub-mongod
for _ in $(seq 1 60); do mongosh --quiet --port 27022 --eval 'db.hello()' >/dev/null 2>&1 && break; sleep 1; done
mongosh --quiet --port 27022 --eval "rs.initiate({_id: 'rs0', members: [{_id: 0, host: '127.0.0.1:27022'}]})" >/dev/null
for _ in $(seq 1 60); do [ "$(mongosh --quiet --port 27022 --eval 'db.hello().isWritablePrimary')" = "true" ] && break; sleep 1; done
systemctl start $UNIT
fi
echo "replica set: $(mongosh --quiet --port 27022 --eval 'db.hello().setName')"
echo "== nginx snippet and bootstrap vhost"
install -m 644 "$SRC/nginx/privapub-headers.conf" /etc/nginx/snippets/privapub-headers.conf
if [ -f /root/.acme.sh/${HOST}_ecc/fullchain.cer ]; then
install -m 644 "$SRC/nginx/$HOST.conf" /etc/nginx/sites-available/$HOST.conf
else
awk '/^server \{/{n++} n==1' "$SRC/nginx/$HOST.conf" > /etc/nginx/sites-available/$HOST.conf
fi
ln -sf /etc/nginx/sites-available/$HOST.conf /etc/nginx/sites-enabled/$HOST.conf
nginx -t
systemctl reload nginx
echo "== certificate"
if [ -f /root/.acme.sh/${HOST}_ecc/fullchain.cer ]; then
echo "$HOST: certificate present"
else
$ACME --issue --server letsencrypt -d $HOST -w /var/www/acme --renew-hook "systemctl reload nginx"
fi
echo "== full vhost"
install -m 644 "$SRC/nginx/$HOST.conf" /etc/nginx/sites-available/$HOST.conf
nginx -t
systemctl reload nginx
echo "setup complete"