#!/bin/bash # # Bitnami MongoDB library # shellcheck disable=SC1090 # shellcheck disable=SC1091 # Load Generic Libraries . /libfile.sh . /liblog.sh . /libservice.sh . /libvalidations.sh . /libos.sh . /libfs.sh . /libnet.sh ######################## # Load global variables used on MongoDB configuration. # Globals: # MONGODB_* # Arguments: # None # Returns: # Series of exports to be used as 'eval' arguments ######################### mongodb_env() { cat <<"EOF" # Paths export MONGODB_VOLUME_DIR="/bitnami" export MONGODB_DATA_DIR="${MONGODB_DATA_DIR:-${MONGODB_VOLUME_DIR}/mongodb/data}" export MONGODB_BASE_DIR="/opt/bitnami/mongodb" export MONGODB_CONF_DIR="$MONGODB_BASE_DIR/conf" export MONGODB_MOUNTED_CONF_DIR="${MONGODB_MOUNTED_CONF_DIR:-${MONGODB_VOLUME_DIR}/conf}" export MONGODB_LOG_DIR="$MONGODB_BASE_DIR/logs" export MONGODB_TMP_DIR="$MONGODB_BASE_DIR/tmp" export MONGODB_BIN_DIR="$MONGODB_BASE_DIR/bin" export MONGODB_TEMPLATES_DIR="$MONGODB_BASE_DIR/templates" export MONGODB_MONGOD_TEMPLATES_FILE="$MONGODB_TEMPLATES_DIR/mongodb.conf.tpl" export MONGODB_CONF_FILE="$MONGODB_CONF_DIR/mongodb.conf" export MONGODB_KEY_FILE="$MONGODB_CONF_DIR/keyfile" export MONGODB_PID_FILE="$MONGODB_TMP_DIR/mongodb.pid" export MONGODB_LOG_FILE="$MONGODB_LOG_DIR/mongodb.log" export MONGODB_INITSCRIPTS_DIR=/docker-entrypoint-initdb.d export PATH="$MONGODB_BIN_DIR:$PATH" # Users export MONGODB_DAEMON_USER="mongo" export MONGODB_DAEMON_GROUP="mongo" # Settings export MONGODB_HOST="${MONGODB_HOST:-}" export MONGODB_MAX_TIMEOUT="${MONGODB_MAX_TIMEOUT:-35}" export MONGODB_PORT_NUMBER="${MONGODB_PORT_NUMBER:-27017}" export MONGODB_ROOT_PASSWORD="${MONGODB_ROOT_PASSWORD:-}" export MONGODB_USERNAME="${MONGODB_USERNAME:-}" export MONGODB_REPLICA_SET_MODE="${MONGODB_REPLICA_SET_MODE:-}" export MONGODB_REPLICA_SET_NAME="${MONGODB_REPLICA_SET_NAME:-replicaset}" export MONGODB_ENABLE_MAJORITY_READ="${MONGODB_ENABLE_MAJORITY_READ:-yes}" export ALLOW_EMPTY_PASSWORD="${ALLOW_EMPTY_PASSWORD:-no}" export MONGODB_EXTRA_FLAGS="${MONGODB_EXTRA_FLAGS:-}" export MONGODB_CLIENT_EXTRA_FLAGS="${MONGODB_CLIENT_EXTRA_FLAGS:-}" export MONGODB_ADVERTISED_HOSTNAME="${MONGODB_ADVERTISED_HOSTNAME:-}" export MONGODB_DATABASE="${MONGODB_DATABASE:-}" export MONGODB_DISABLE_SYSTEM_LOG="${MONGODB_DISABLE_SYSTEM_LOG:-no}" export MONGODB_ENABLE_DIRECTORY_PER_DB="${MONGODB_ENABLE_DIRECTORY_PER_DB:-no}" export MONGODB_ENABLE_IPV6="${MONGODB_ENABLE_IPV6:-no}" export MONGODB_PRIMARY_HOST="${MONGODB_PRIMARY_HOST:-}" export MONGODB_PRIMARY_PORT_NUMBER="${MONGODB_PRIMARY_PORT_NUMBER:-27017}" export MONGODB_PRIMARY_ROOT_PASSWORD="${MONGODB_PRIMARY_ROOT_PASSWORD:-}" export MONGODB_PRIMARY_ROOT_USER="${MONGODB_PRIMARY_ROOT_USER:-root}" export MONGODB_SYSTEM_LOG_VERBOSITY="${MONGODB_SYSTEM_LOG_VERBOSITY:-0}" EOF if [[ -f "${MONGODB_PASSWORD_FILE:-}" ]]; then cat <<"EOF" export MONGODB_PASSWORD="$(< "${MONGODB_PASSWORD_FILE}")" EOF else cat <<"EOF" export MONGODB_PASSWORD="${MONGODB_PASSWORD:-}" EOF fi if [[ -f "${MONGODB_ROOT_PASSWORD_FILE:-}" ]]; then cat <<"EOF" export MONGODB_ROOT_PASSWORD="$(< "${MONGODB_ROOT_PASSWORD_FILE}")" EOF else cat <<"EOF" export MONGODB_ROOT_PASSWORD="${MONGODB_ROOT_PASSWORD:-}" EOF fi if [[ -f "${MONGODB_PRIMARY_ROOT_PASSWORD_FILE:-}" ]]; then cat <<"EOF" export MONGODB_PRIMARY_ROOT_PASSWORD="$(< "${MONGODB_PRIMARY_ROOT_PASSWORD_FILE}")" EOF else cat <<"EOF" export MONGODB_PRIMARY_ROOT_PASSWORD="${MONGODB_PRIMARY_ROOT_PASSWORD:-}" EOF fi if [[ -f "${MONGODB_REPLICA_SET_KEY_FILE:-}" ]]; then cat <<"EOF" export MONGODB_REPLICA_SET_KEY="$(< "${MONGODB_REPLICA_SET_KEY_FILE}")" EOF else cat <<"EOF" export MONGODB_REPLICA_SET_KEY="${MONGODB_REPLICA_SET_KEY:-}" EOF fi } ######################## # Validate settings in MONGODB_* env. variables # Globals: # MONGODB_* # Arguments: # None # Returns: # None ######################### mongodb_validate() { info "Validating settings in MONGODB_* env vars..." local error_message="" local -r replicaset_error_message="In order to configure MongoDB replica set authentication you \ need to provide the MONGODB_REPLICA_SET_KEY on every node, specify MONGODB_ROOT_PASSWORD \ in the primary node and MONGODB_PRIMARY_ROOT_PASSWORD in the rest of nodes" local error_code=0 # Auxiliary functions print_validation_error() { error "$1" error_code=1 } if [[ -n "$MONGODB_REPLICA_SET_MODE" ]]; then if [[ -z "$MONGODB_ADVERTISED_HOSTNAME" ]]; then warn "In order to use hostnames instead of IPs your should set MONGODB_ADVERTISED_HOSTNAME" fi if [[ "$MONGODB_REPLICA_SET_MODE" =~ ^(secondary|arbiter) ]]; then if [[ -z "$MONGODB_PRIMARY_HOST" ]]; then error_message="In order to configure MongoDB as secondary or arbiter node \ you need to provide the MONGODB_PRIMARY_HOST env var" print_validation_error "$error_message" fi if { [[ -n "$MONGODB_PRIMARY_ROOT_PASSWORD" ]] && [[ -z "$MONGODB_REPLICA_SET_KEY" ]]; } || \ { [[ -z "$MONGODB_PRIMARY_ROOT_PASSWORD" ]] && [[ -n "$MONGODB_REPLICA_SET_KEY" ]]; }; then print_validation_error "$replicaset_error_message" fi if [[ -n "$MONGODB_ROOT_PASSWORD" ]]; then error_message="MONGODB_ROOT_PASSWORD shouldn't be set on a 'non-primary' node" print_validation_error "$error_message" fi elif [[ "$MONGODB_REPLICA_SET_MODE" = "primary" ]]; then if { [[ -n "$MONGODB_ROOT_PASSWORD" ]] && [[ -z "$MONGODB_REPLICA_SET_KEY" ]] ;} || \ { [[ -z "$MONGODB_ROOT_PASSWORD" ]] && [[ -n "$MONGODB_REPLICA_SET_KEY" ]] ;}; then print_validation_error "$replicaset_error_message" fi if [[ -n "$MONGODB_PRIMARY_ROOT_PASSWORD" ]]; then error_message="MONGODB_PRIMARY_ROOT_PASSWORD shouldn't be set on a 'primary' node" print_validation_error "$error_message" fi if [[ -z "$MONGODB_ROOT_PASSWORD" ]] && ! is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then error_message="The MONGODB_ROOT_PASSWORD environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is only recommended for development." print_validation_error "$error_message" fi else error_message="You set the environment variable MONGODB_REPLICA_SET_MODE with an invalid value. \ Available options are 'primary/secondary/arbiter'" print_validation_error "$error_message" fi fi if [[ -n "$MONGODB_REPLICA_SET_KEY" ]] && (( ${#MONGODB_REPLICA_SET_KEY} < 5 )); then error_message="MONGODB_REPLICA_SET_KEY must be, at least, 5 characters long!" print_validation_error "$error_message" fi if is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then warn "You set the environment variable ALLOW_EMPTY_PASSWORD=${ALLOW_EMPTY_PASSWORD}. For safety reasons, do not use this flag in a production environment." elif [[ -n "$MONGODB_USERNAME" ]] && [[ -z "$MONGODB_PASSWORD" ]]; then error_message="The MONGODB_PASSWORD environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is only recommended for development." print_validation_error "$error_message" fi [[ "$error_code" -eq 0 ]] || exit "$error_code" } ######################## # Copy mounted configuration files # Globals: # MONGODB_* # Arguments: # None # Returns: # None ######################### mongodb_copy_mounted_config() { if ! is_dir_empty "$MONGODB_MOUNTED_CONF_DIR"; then if ! cp -Lr "$MONGODB_MOUNTED_CONF_DIR"/* "$MONGODB_CONF_DIR"; then error "Issue copying mounted configuration files from $MONGODB_MOUNTED_CONF_DIR to $MONGODB_CONF_DIR. Make sure you are not mounting configuration files in $MONGODB_CONF_DIR and $MONGODB_MOUNTED_CONF_DIR at the same time" exit 1 fi fi } ######################## # Create MongoDB configuration (mongodb.conf) file # Globals: # MONGODB_* # Arguments: # None # Returns: # None ######################### mongodb_create_mongod_config() { debug "Creating main configuration file..." render-template "$MONGODB_MONGOD_TEMPLATES_FILE" > "$MONGODB_CONF_FILE" } ######################## # Execute an arbitrary query/queries against the running MongoDB service # Stdin: # Query/queries to execute # Arguments: # $1 - User to run queries # $2 - Password # $3 - Database where to run the queries # $4 - Host (default to result of get_mongo_hostname function) # $5 - Port (default $MONGODB_PORT_NUMBER) # $6 - Extra arguments (default $MONGODB_CLIENT_EXTRA_FLAGS) # Returns: # None ######################## mongodb_execute() { local -r user="${1:-}" local -r password="${2:-}" local -r database="${3:-}" local -r host="${4:-$(get_mongo_hostname)}" local -r port="${5:-$MONGODB_PORT_NUMBER}" local -r extra_args="${6:-$MONGODB_CLIENT_EXTRA_FLAGS}" local result local final_user="$user" # If password is empty it means no auth, do not specify user [[ -z "$password" ]] && final_user="" local -a args=("--host" "$host" "--port" "$port") [[ -n "$final_user" ]] && args+=("-u" "$final_user") [[ -n "$password" ]] && args+=("-p" "$password") [[ -n "$extra_args" ]] && args+=($extra_args) [[ -n "$database" ]] && args+=("$database") "$MONGODB_BIN_DIR/mongo" "${args[@]}" } ######################## # Determine the hostname by which to contact the locally running mongo daemon # Returns: # The value of $MONGODB_ADVERTISED_HOSTNAME or the current host address ######################## get_mongo_hostname() { if [[ -n "$MONGODB_ADVERTISED_HOSTNAME" ]]; then echo "$MONGODB_ADVERTISED_HOSTNAME" else get_machine_ip fi } ######################## # Drop local Database # Globals: # MONGODB_* # Arguments: # None # Returns: # None ######################### mongodb_drop_local_database() { info "Dropping local database to reset replica set setup..." local command=("mongodb_execute") [[ -n "$MONGODB_PASSWORD" ]] && command=("${command[@]}" "$MONGODB_USERNAME" "$MONGODB_PASSWORD") "${command[@]}" </dev/null < "$MONGODB_CONF_FILE" } ######################## # Change bind ip address to 0.0.0.0 # Globals: # MONGODB_* # Arguments: # None # Returns: # None ######################### mongodb_set_listen_all_conf() { if ! mongodb_is_file_external "mongodb.conf"; then mongodb_config_apply_regex "#?bindIp:.*" "#bindIp:" mongodb_config_apply_regex "#?bindIpAll:.*" "bindIpAll: true" else debug "mongodb.conf mounted. Skipping IP binding to all addresses" fi } ######################## # Enable Auth # Globals: # MONGODB_* # Arguments: # None # Return # None ######################### mongodb_set_auth_conf() { local authorization if ! mongodb_is_file_external "mongodb.conf"; then if [[ -n "$MONGODB_ROOT_PASSWORD" ]] || [[ -n "$MONGODB_PASSWORD" ]]; then authorization="$(yq read "$MONGODB_CONF_FILE" security.authorization)" if [[ "$authorization" = "disabled" ]]; then info "Enabling authentication..." # TODO: replace 'sed' calls with 'yq' once 'yq write' does not remove comments mongodb_config_apply_regex "#?authorization:.*" "authorization: enabled" mongodb_config_apply_regex "#?enableLocalhostAuthBypass:.*" "enableLocalhostAuthBypass: false" fi fi else debug "mongodb.conf mounted. Skipping authorization enabling" fi } ######################## # Enable ReplicaSetMode # Globals: # MONGODB_* # Arguments: # None # Returns: # None ######################### mongodb_set_replicasetmode_conf() { if ! mongodb_is_file_external "mongodb.conf"; then mongodb_config_apply_regex "#?replication:.*" "replication:" mongodb_config_apply_regex "#?replSetName:.*" "replSetName: $MONGODB_REPLICA_SET_NAME" mongodb_config_apply_regex "#?enableMajorityReadConcern:.*" "enableMajorityReadConcern: $({ is_boolean_yes "$MONGODB_ENABLE_MAJORITY_READ" && echo 'true';} || echo 'false')" else debug "mongodb.conf mounted. Skipping replicaset mode enabling" fi } ######################## # Create the appropriate users # Globals: # MONGODB_* # Arguments: # None # Returns: # None ######################### mongodb_create_users() { local result info "Creating users..." if [[ -n "$MONGODB_ROOT_PASSWORD" ]] && ! [[ "$MONGODB_REPLICA_SET_MODE" =~ ^(secondary|arbiter) ]]; then info "Creating root user..." result=$(mongodb_execute "" "" "" "127.0.0.1" < "$MONGODB_KEY_FILE" chmod 600 "$MONGODB_KEY_FILE" if am_i_root; then configure_permissions "$MONGODB_KEY_FILE" "$MONGODB_DAEMON_USER" "$MONGODB_DAEMON_GROUP" "" "600" else chmod 600 "$MONGODB_KEY_FILE" fi else debug "keyfile mounted. Skipping keyfile generation" fi } ######################## # Get if primary node is initialized # Globals: # MONGODB_* # Arguments: # $1 - node # Returns: # None ######################### mongodb_is_primary_node_initiated() { local node="${1:?node is required}" local result result=$(mongodb_execute "root" "$MONGODB_ROOT_PASSWORD" "admin" "127.0.0.1" "$MONGODB_PORT_NUMBER" < $tmp_file while read -r f; do case "$f" in *.sh) if [[ -x "$f" ]]; then debug "Executing $f"; "$f" else debug "Sourcing $f"; . "$f" fi ;; *.js) debug "Executing $f"; mongodb_execute "$mongo_user" "$mongo_pass" < "$f";; *.js.gz) debug "Executing $f"; gunzip -c "$f" | mongodb_execute "$mongo_user" "$mongo_pass";; *) debug "Ignoring $f" ;; esac done < $tmp_file touch "$MONGODB_VOLUME_DIR"/.user_scripts_initialized fi }