mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-08-15 08:20:33 +02:00
177 lines
4.6 KiB
Bash
Executable File
177 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
repository_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
primary_files=(
|
|
-f "$repository_root/docker-compose.yml"
|
|
-f "$repository_root/docker-compose.replica-primary.yml"
|
|
)
|
|
secondary_files=(
|
|
-f "$repository_root/docker-compose.replica-secondary.yml"
|
|
)
|
|
|
|
compose_prefix=(docker compose)
|
|
if [[ -n "${COMPOSE_ENV_FILE:-}" ]]; then
|
|
compose_prefix+=(--env-file "$COMPOSE_ENV_FILE")
|
|
fi
|
|
|
|
keyfile="${MONGO_REPLICA_KEYFILE:-$repository_root/secrets/mongo-replica-keyfile}"
|
|
if [[ "$keyfile" != /* ]]; then
|
|
keyfile="$repository_root/${keyfile#./}"
|
|
fi
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage:
|
|
./scripts/mongodb-replica.sh generate-key
|
|
./scripts/mongodb-replica.sh primary-up <primary-hostname:port>
|
|
./scripts/mongodb-replica.sh secondary-up
|
|
./scripts/mongodb-replica.sh add-secondary <secondary-hostname:port> [delay-seconds]
|
|
./scripts/mongodb-replica.sh status
|
|
|
|
Environment:
|
|
COMPOSE_ENV_FILE Optional Compose environment file.
|
|
MONGO_REPLICA_KEYFILE Shared keyfile path (default: ./secrets/mongo-replica-keyfile).
|
|
|
|
The primary and secondary hostnames must resolve from both MongoDB servers.
|
|
The remote member is added hidden, priority 0, and non-voting so a WAN outage
|
|
cannot block production majority writes. Set delay-seconds to 0 for no delay.
|
|
USAGE
|
|
}
|
|
|
|
validate_host() {
|
|
if [[ ! "$1" =~ ^[A-Za-z0-9.-]+:[0-9]{1,5}$ ]]; then
|
|
echo "Expected a resolvable hostname and port, for example mongo-primary.internal:27017" >&2
|
|
exit 2
|
|
fi
|
|
}
|
|
|
|
validate_delay() {
|
|
if [[ ! "$1" =~ ^[0-9]+$ ]]; then
|
|
echo "Delay must be a non-negative number of seconds" >&2
|
|
exit 2
|
|
fi
|
|
}
|
|
|
|
require_keyfile() {
|
|
if [[ ! -s "$keyfile" ]]; then
|
|
echo "Replica keyfile not found: $keyfile" >&2
|
|
echo "Run ./scripts/mongodb-replica.sh generate-key first." >&2
|
|
exit 2
|
|
fi
|
|
}
|
|
|
|
primary_compose() {
|
|
"${compose_prefix[@]}" "${primary_files[@]}" "$@"
|
|
}
|
|
|
|
secondary_compose() {
|
|
"${compose_prefix[@]}" "${secondary_files[@]}" "$@"
|
|
}
|
|
|
|
mongo_eval() {
|
|
local javascript="$1"
|
|
primary_compose exec -T -e "MONGO_RS_EVAL=$javascript" mongodb sh -eu -c \
|
|
'mongosh --quiet \
|
|
--username="$MONGO_INITDB_ROOT_USERNAME" \
|
|
--password="$MONGO_INITDB_ROOT_PASSWORD" \
|
|
--authenticationDatabase=admin \
|
|
--eval "$MONGO_RS_EVAL"'
|
|
}
|
|
|
|
wait_for_primary_container() {
|
|
local attempt
|
|
for attempt in $(seq 1 60); do
|
|
if primary_compose exec -T mongodb sh -eu -c \
|
|
'mongosh --quiet \
|
|
--username="$MONGO_INITDB_ROOT_USERNAME" \
|
|
--password="$MONGO_INITDB_ROOT_PASSWORD" \
|
|
--authenticationDatabase=admin \
|
|
--eval "db.adminCommand({ ping: 1 }).ok"' >/dev/null 2>&1; then
|
|
return
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "MongoDB did not become ready within 120 seconds" >&2
|
|
exit 1
|
|
}
|
|
|
|
command="${1:-}"
|
|
case "$command" in
|
|
generate-key)
|
|
if [[ -e "$keyfile" ]]; then
|
|
echo "Refusing to overwrite existing keyfile: $keyfile" >&2
|
|
exit 2
|
|
fi
|
|
mkdir -p "$(dirname "$keyfile")"
|
|
umask 077
|
|
openssl rand -base64 756 > "$keyfile"
|
|
chmod 600 "$keyfile"
|
|
echo "Created $keyfile"
|
|
echo "Copy this exact file securely to the secondary server."
|
|
;;
|
|
|
|
primary-up)
|
|
primary_host="${2:-}"
|
|
validate_host "$primary_host"
|
|
require_keyfile
|
|
primary_compose up -d mongodb
|
|
wait_for_primary_container
|
|
mongo_eval "
|
|
try {
|
|
const status = rs.status();
|
|
print('Replica set already initialized as ' + status.set);
|
|
} catch (error) {
|
|
if (error.code !== 94 && error.codeName !== 'NotYetInitialized') throw error;
|
|
printjson(rs.initiate({
|
|
_id: 'rs0',
|
|
members: [{ _id: 0, host: '$primary_host', priority: 1, votes: 1 }]
|
|
}));
|
|
}
|
|
"
|
|
;;
|
|
|
|
secondary-up)
|
|
require_keyfile
|
|
secondary_compose up -d mongodb-secondary
|
|
;;
|
|
|
|
add-secondary)
|
|
secondary_host="${2:-}"
|
|
delay="${3:-3600}"
|
|
validate_host "$secondary_host"
|
|
validate_delay "$delay"
|
|
mongo_eval "
|
|
const host = '$secondary_host';
|
|
const existing = rs.conf().members.find((member) => member.host === host);
|
|
if (existing) {
|
|
print('Secondary already configured: ' + host);
|
|
} else {
|
|
printjson(rs.add({
|
|
host,
|
|
priority: 0,
|
|
votes: 0,
|
|
hidden: true,
|
|
secondaryDelaySecs: $delay
|
|
}));
|
|
}
|
|
"
|
|
;;
|
|
|
|
status)
|
|
mongo_eval "
|
|
printjson(rs.status().members.map((member) => ({
|
|
name: member.name,
|
|
state: member.stateStr,
|
|
health: member.health,
|
|
replicationTime: member.optimeDate
|
|
})));
|
|
"
|
|
;;
|
|
|
|
*)
|
|
usage
|
|
[[ -n "$command" ]] && exit 2
|
|
;;
|
|
esac
|