mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-08-15 08:20:33 +02:00
feat: add optional MongoDB replica deployment (#765)
This commit is contained in:
Executable
+176
@@ -0,0 +1,176 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user