#!/usr/bin/env bash set -Eeuo pipefail readonly CLOUDFLARE_HOSTNAME="test-mongo-local.blr.streamoid.com" readonly LOCAL_PORT="27017" readonly ENV_FILE="/etc/mongo-tunnel.env" readonly SERVICE_FILE="/etc/systemd/system/mongo-tunnel.service" readonly HELPER_FILE="/usr/local/bin/mongo-tunnel" readonly SERVICE_NAME="mongo-tunnel.service" if [[ -t 1 ]]; then readonly RED=$'\033[0;31m' readonly GREEN=$'\033[0;32m' readonly YELLOW=$'\033[0;33m' readonly BLUE=$'\033[0;34m' readonly CYAN=$'\033[0;36m' readonly BOLD=$'\033[1m' readonly RESET=$'\033[0m' else readonly RED="" readonly GREEN="" readonly YELLOW="" readonly BLUE="" readonly CYAN="" readonly BOLD="" readonly RESET="" fi log_info() { printf '%s[INFO]%s %s\n' "${BLUE}" "${RESET}" "$*" } log_success() { printf '%s[OK]%s %s\n' "${GREEN}" "${RESET}" "$*" } log_warning() { printf '%s[WARNING]%s %s\n' "${YELLOW}" "${RESET}" "$*" >&2 } log_error() { printf '%s[ERROR]%s %s\n' "${RED}" "${RESET}" "$*" >&2 } die() { log_error "$*" exit 1 } on_error() { local exit_code=$? local line_number="${1:-unknown}" log_error "Installation failed at line ${line_number} with exit code ${exit_code}." if systemctl cat "${SERVICE_NAME}" >/dev/null 2>&1; then printf '\n%sRecent service logs:%s\n' "${BOLD}" "${RESET}" >&2 journalctl \ -u "${SERVICE_NAME}" \ -n 40 \ --no-pager \ --no-hostname >&2 || true fi exit "${exit_code}" } trap 'on_error "$LINENO"' ERR require_root() { if [[ "${EUID}" -ne 0 ]]; then die "This installer must run as root. Use: curl -fsSL https://install.test.streamoid.com/mongo | sudo bash" fi } check_operating_system() { [[ -r /etc/os-release ]] || die "Unable to identify the operating system." # shellcheck disable=SC1091 source /etc/os-release [[ "${ID:-}" == "ubuntu" ]] || die "This installer supports Ubuntu only." case "${VERSION_ID:-}" in 22.04|24.04) log_success "Detected Ubuntu ${VERSION_ID}." ;; *) die "Unsupported Ubuntu version: ${VERSION_ID:-unknown}. Supported versions are Ubuntu 22.04 and 24.04." ;; esac } install_dependencies() { log_info "Checking required packages..." export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y --no-install-recommends \ ca-certificates \ curl \ gnupg \ iproute2 log_success "Required packages are installed." } install_cloudflared() { if command -v cloudflared >/dev/null 2>&1; then log_success "cloudflared is already installed: $(cloudflared --version 2>/dev/null | head -n 1)" return fi log_info "Installing cloudflared from Cloudflare's official APT repository..." install -d -m 0755 /usr/share/keyrings local temporary_key temporary_key="$(mktemp)" curl -fsSL \ https://pkg.cloudflare.com/cloudflare-main.gpg \ -o "${temporary_key}" gpg --dearmor \ --yes \ --output /usr/share/keyrings/cloudflare-main.gpg \ "${temporary_key}" rm -f "${temporary_key}" chown root:root /usr/share/keyrings/cloudflare-main.gpg chmod 0644 /usr/share/keyrings/cloudflare-main.gpg cat > /etc/apt/sources.list.d/cloudflared.list <<'EOF' deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main EOF chown root:root /etc/apt/sources.list.d/cloudflared.list chmod 0644 /etc/apt/sources.list.d/cloudflared.list apt-get update -qq apt-get install -y --no-install-recommends cloudflared command -v cloudflared >/dev/null 2>&1 || die "cloudflared installation completed, but the executable was not found." log_success "Installed $(cloudflared --version 2>/dev/null | head -n 1)." } read_existing_value() { local key="$1" [[ -r "${ENV_FILE}" ]] || return 0 awk -v key="${key}" ' $0 ~ "^[[:space:]]*" key "=" { value = substr($0, index($0, "=") + 1) if (value ~ /^".*"$/) { value = substr(value, 2, length(value) - 2) gsub(/\\"/, "\"", value) gsub(/\\\\/, "\\", value) } print value exit } ' "${ENV_FILE}" } prompt_value() { local prompt_text="$1" local default_value="${2:-}" local hide_input="${3:-false}" local value="" [[ -r /dev/tty ]] || die "Interactive input is required, but /dev/tty is unavailable." if [[ "${hide_input}" == "true" ]]; then if [[ -n "${default_value}" ]]; then printf '%s%s%s [press Enter to keep existing]: ' \ "${BOLD}" \ "${prompt_text}" \ "${RESET}" > /dev/tty else printf '%s%s%s: ' \ "${BOLD}" \ "${prompt_text}" \ "${RESET}" > /dev/tty fi IFS= read -r -s value < /dev/tty printf '\n' > /dev/tty else if [[ -n "${default_value}" ]]; then printf '%s%s%s [%s]: ' \ "${BOLD}" \ "${prompt_text}" \ "${RESET}" \ "${default_value}" > /dev/tty else printf '%s%s%s: ' \ "${BOLD}" \ "${prompt_text}" \ "${RESET}" > /dev/tty fi IFS= read -r value < /dev/tty fi if [[ -z "${value}" ]]; then value="${default_value}" fi printf '%s' "${value}" } validate_credential() { local credential_name="$1" local credential_value="$2" [[ -n "${credential_value}" ]] || die "${credential_name} cannot be empty." [[ "${credential_value}" != *$'\n'* ]] || die "${credential_name} cannot contain a newline." [[ "${credential_value}" != *$'\r'* ]] || die "${credential_name} cannot contain a carriage return." [[ "${credential_value}" != *" "* ]] || die "${credential_name} cannot contain spaces." } escape_environment_value() { local value="$1" value="${value//\\/\\\\}" value="${value//\"/\\\"}" printf '"%s"' "${value}" } check_port_availability() { if ! ss -H -ltn "sport = :${LOCAL_PORT}" 2>/dev/null | grep -q .; then return 0 fi if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then log_info "Port ${LOCAL_PORT} is used by the existing MongoDB tunnel and will be reused." return 0 fi die "TCP port ${LOCAL_PORT} is already in use by another application." } write_environment_file() { local token_id="$1" local token_secret="$2" local temporary_file temporary_file="$(mktemp)" { printf 'CLOUDFLARE_HOSTNAME=%s\n' \ "$(escape_environment_value "${CLOUDFLARE_HOSTNAME}")" printf 'CF_ACCESS_CLIENT_ID=%s\n' \ "$(escape_environment_value "${token_id}")" printf 'CF_ACCESS_CLIENT_SECRET=%s\n' \ "$(escape_environment_value "${token_secret}")" printf 'LOCAL_PORT=%s\n' \ "$(escape_environment_value "${LOCAL_PORT}")" } > "${temporary_file}" chown root:root "${temporary_file}" chmod 0600 "${temporary_file}" mv -f "${temporary_file}" "${ENV_FILE}" chown root:root "${ENV_FILE}" chmod 0600 "${ENV_FILE}" log_success "Configuration saved securely to ${ENV_FILE}." } write_systemd_service() { cat > "${SERVICE_FILE}" <<'EOF' [Unit] Description=Cloudflare Access TCP tunnel for MongoDB Documentation=https://developers.cloudflare.com/cloudflare-one/access-controls/applications/non-http/cloudflared-authentication/arbitrary-tcp/ Wants=network-online.target After=network-online.target StartLimitIntervalSec=60 StartLimitBurst=10 [Service] Type=simple User=root Group=root EnvironmentFile=/etc/mongo-tunnel.env ExecStart=/usr/bin/cloudflared access tcp --hostname ${CLOUDFLARE_HOSTNAME} --url 127.0.0.1:${LOCAL_PORT} --service-token-id ${CF_ACCESS_CLIENT_ID} --service-token-secret ${CF_ACCESS_CLIENT_SECRET} --loglevel info Restart=always RestartSec=5 TimeoutStartSec=30 TimeoutStopSec=15 KillSignal=SIGTERM UMask=0077 NoNewPrivileges=true PrivateTmp=true ProtectHome=true ProtectSystem=strict ProtectKernelTunables=true ProtectKernelModules=true ProtectControlGroups=true RestrictRealtime=true RestrictSUIDSGID=true LockPersonality=true SystemCallArchitectures=native [Install] WantedBy=multi-user.target EOF chown root:root "${SERVICE_FILE}" chmod 0644 "${SERVICE_FILE}" log_success "Systemd service created at ${SERVICE_FILE}." } write_helper_cli() { cat > "${HELPER_FILE}" <<'HELPER_EOF' #!/usr/bin/env bash set -Eeuo pipefail readonly SERVICE_NAME="mongo-tunnel.service" readonly ENV_FILE="/etc/mongo-tunnel.env" readonly SERVICE_FILE="/etc/systemd/system/mongo-tunnel.service" readonly HELPER_FILE="/usr/local/bin/mongo-tunnel" if [[ -t 1 ]]; then readonly RED=$'\033[0;31m' readonly GREEN=$'\033[0;32m' readonly YELLOW=$'\033[0;33m' readonly BLUE=$'\033[0;34m' readonly CYAN=$'\033[0;36m' readonly BOLD=$'\033[1m' readonly RESET=$'\033[0m' else readonly RED="" readonly GREEN="" readonly YELLOW="" readonly BLUE="" readonly CYAN="" readonly BOLD="" readonly RESET="" fi info() { printf '%s[INFO]%s %s\n' "${BLUE}" "${RESET}" "$*" } success() { printf '%s[OK]%s %s\n' "${GREEN}" "${RESET}" "$*" } warning() { printf '%s[WARNING]%s %s\n' "${YELLOW}" "${RESET}" "$*" >&2 } error() { printf '%s[ERROR]%s %s\n' "${RED}" "${RESET}" "$*" >&2 } require_root() { local command_name="${1:-command}" if [[ "${EUID}" -ne 0 ]]; then error "The ${command_name} command requires root privileges." error "Run: sudo mongo-tunnel ${command_name}" exit 1 fi } read_env_value() { local key="$1" [[ -r "${ENV_FILE}" ]] || return 0 awk -v key="${key}" ' $0 ~ "^[[:space:]]*" key "=" { value = substr($0, index($0, "=") + 1) if (value ~ /^".*"$/) { value = substr(value, 2, length(value) - 2) } print value exit } ' "${ENV_FILE}" } show_usage() { cat </dev/null || true )" if printf '%s\n' "${recent_logs}" | grep -Eiq \ 'bad handshake|authentication failed|unauthorized|forbidden|status[=: ]+401|status[=: ]+403|http[^0-9]*401|http[^0-9]*403|response[^0-9]*401|response[^0-9]*403'; then printf '\n' warning "Cloudflare tunnel is listening locally, but authentication to Cloudflare failed." printf '\n%sPlease verify:%s\n\n' "${BOLD}" "${RESET}" printf ' • Service Token ID\n' printf ' • Service Token Secret\n' printf ' • Access policy (Service Auth)\n' printf ' • Hostname\n' printf '\n%sDetected Cloudflare errors:%s\n' "${BOLD}" "${RESET}" printf '%s\n' "${recent_logs}" | grep -Ei \ 'bad handshake|authentication failed|unauthorized|forbidden|status[=: ]+401|status[=: ]+403|http[^0-9]*401|http[^0-9]*403|response[^0-9]*401|response[^0-9]*403' | tail -n 10 || true return 1 fi printf '\n%s✓ Cloudflare Access authentication successful.%s\n' \ "${GREEN}" "${RESET}" printf '%s✓ MongoDB tunnel is ready.%s\n' \ "${GREEN}" "${RESET}" return 0 } run_health_check() { local port="" local check_started="" if [[ -r "${ENV_FILE}" ]]; then port="$(read_env_value LOCAL_PORT)" fi [[ -n "${port}" ]] || { error "LOCAL_PORT is missing from ${ENV_FILE}." return 1 } check_started="$(date '+%Y-%m-%d %H:%M:%S')" if ! systemctl is-active --quiet "${SERVICE_NAME}"; then error "MongoDB tunnel service is not running." return 1 fi if ! ss -H -ltn "sport = :${port}" 2>/dev/null | grep -q .; then error "Nothing is listening on 127.0.0.1:${port}." return 1 fi success "Tunnel is listening locally on 127.0.0.1:${port}." info "Waiting briefly for Cloudflare authentication errors..." sleep 3 check_recent_errors "${check_started}" } show_status() { local port="" local hostname="" if [[ -r "${ENV_FILE}" ]]; then port="$(read_env_value LOCAL_PORT)" hostname="$(read_env_value CLOUDFLARE_HOSTNAME)" fi printf '\n%sMongoDB tunnel configuration%s\n' "${BOLD}" "${RESET}" printf ' Cloudflare hostname: %s\n' "${hostname:-unknown}" printf ' Local address: 127.0.0.1:%s\n' "${port:-unknown}" if [[ -n "${port}" ]]; then printf ' Connection string: mongodb://127.0.0.1:%s\n' "${port}" fi printf '\n' systemctl --no-pager --full status "${SERVICE_NAME}" || true printf '\n' if systemctl is-active --quiet "${SERVICE_NAME}"; then success "Service is active and running." else error "Service is not running." return 1 fi if [[ -n "${port}" ]] && ss -H -ltn "sport = :${port}" 2>/dev/null | grep -q .; then success "Tunnel is listening on 127.0.0.1:${port}." else error "The local tunnel port is not listening." return 1 fi } start_tunnel() { require_root start local started_at started_at="$(date '+%Y-%m-%d %H:%M:%S')" systemctl start "${SERVICE_NAME}" success "MongoDB tunnel started." sleep 1 show_status info "Waiting briefly for Cloudflare authentication errors..." sleep 3 check_recent_errors "${started_at}" } stop_tunnel() { require_root stop systemctl stop "${SERVICE_NAME}" success "MongoDB tunnel stopped." } restart_tunnel() { require_root restart local started_at started_at="$(date '+%Y-%m-%d %H:%M:%S')" systemctl restart "${SERVICE_NAME}" success "MongoDB tunnel restarted." sleep 1 show_status info "Waiting briefly for Cloudflare authentication errors..." sleep 3 check_recent_errors "${started_at}" } show_logs() { journalctl \ -u "${SERVICE_NAME}" \ -f \ --no-hostname } uninstall_tunnel() { require_root uninstall printf '%sThis will remove the MongoDB tunnel and stored credentials.%s\n' \ "${YELLOW}" "${RESET}" printf 'Continue? [y/N]: ' local answer="" IFS= read -r answer case "${answer}" in y|Y|yes|YES|Yes) ;; *) info "Uninstall cancelled." return 0 ;; esac systemctl disable --now "${SERVICE_NAME}" >/dev/null 2>&1 || true rm -f "${SERVICE_FILE}" rm -f "${ENV_FILE}" systemctl daemon-reload systemctl reset-failed "${SERVICE_NAME}" >/dev/null 2>&1 || true success "MongoDB tunnel service removed." success "Stored Cloudflare credentials removed." rm -f "${HELPER_FILE}" } command="${1:-}" case "${command}" in status) show_status ;; start) start_tunnel ;; stop) stop_tunnel ;; restart) restart_tunnel ;; logs) show_logs ;; check) run_health_check ;; connection) show_connection ;; uninstall) uninstall_tunnel ;; help|-h|--help|"") show_usage ;; *) error "Unknown command: ${command}" show_usage exit 1 ;; esac HELPER_EOF chown root:root "${HELPER_FILE}" chmod 0755 "${HELPER_FILE}" log_success "Helper command created at ${HELPER_FILE}." } wait_for_listener() { local attempt log_info "Waiting for the tunnel to listen on 127.0.0.1:${LOCAL_PORT}..." for attempt in $(seq 1 30); do if ss -H -ltn "sport = :${LOCAL_PORT}" 2>/dev/null | awk '{print $4}' | grep -Eq "^(127\.0\.0\.1|\[::1\]|localhost):${LOCAL_PORT}$"; then log_success "Verified that 127.0.0.1:${LOCAL_PORT} is listening." return 0 fi if ! systemctl is-active --quiet "${SERVICE_NAME}"; then break fi sleep 1 done log_error "The tunnel did not begin listening on 127.0.0.1:${LOCAL_PORT}." printf '\n%sRecent service logs:%s\n' "${BOLD}" "${RESET}" >&2 journalctl \ -u "${SERVICE_NAME}" \ -n 40 \ --no-pager \ --no-hostname >&2 || true return 1 } check_cloudflare_authentication() { local service_start_time="$1" local recent_logs="" log_info "Waiting for Cloudflare Access authentication checks..." sleep 3 recent_logs="$( journalctl \ -u "${SERVICE_NAME}" \ --since "${service_start_time}" \ --no-pager \ --no-hostname \ --output=cat 2>/dev/null || true )" if printf '%s\n' "${recent_logs}" | grep -Eiq \ 'bad handshake|authentication failed|unauthorized|forbidden|status[=: ]+401|status[=: ]+403|http[^0-9]*401|http[^0-9]*403|response[^0-9]*401|response[^0-9]*403'; then printf '\n' log_warning "Cloudflare tunnel is listening locally, but authentication to Cloudflare failed." printf '\n%sPlease verify:%s\n\n' "${BOLD}" "${RESET}" printf ' • Service Token ID\n' printf ' • Service Token Secret\n' printf ' • Access policy (Service Auth)\n' printf ' • Hostname\n' printf '\n%sDetected Cloudflare errors:%s\n' "${BOLD}" "${RESET}" printf '%s\n' "${recent_logs}" | grep -Ei \ 'bad handshake|authentication failed|unauthorized|forbidden|status[=: ]+401|status[=: ]+403|http[^0-9]*401|http[^0-9]*403|response[^0-9]*401|response[^0-9]*403' | tail -n 10 || true printf '\n%sThe service remains installed so you can correct the configuration.%s\n' \ "${YELLOW}" "${RESET}" printf '%sRun this command after correcting Cloudflare:%s\n' \ "${YELLOW}" "${RESET}" printf ' sudo mongo-tunnel restart\n\n' return 1 fi printf '\n%s✓ Cloudflare Access authentication successful.%s\n' \ "${GREEN}" "${RESET}" printf '%s✓ MongoDB tunnel is ready.%s\n' \ "${GREEN}" "${RESET}" return 0 } print_completion() { local authentication_status="$1" printf '\n' printf '%s============================================================%s\n' \ "${GREEN}" "${RESET}" printf '%s MongoDB Cloudflare tunnel installation completed%s\n' \ "${BOLD}${GREEN}" "${RESET}" printf '%s============================================================%s\n' \ "${GREEN}" "${RESET}" printf '\n%sConfiguration:%s\n' "${BOLD}" "${RESET}" printf ' Cloudflare hostname: %s\n' "${CLOUDFLARE_HOSTNAME}" printf ' Local tunnel: 127.0.0.1:%s\n' "${LOCAL_PORT}" printf ' Configuration file: %s\n' "${ENV_FILE}" printf ' Systemd service: %s\n' "${SERVICE_FILE}" printf '\n%sMongoDB connection string:%s\n' "${BOLD}" "${RESET}" printf ' mongodb://127.0.0.1:%s\n' "${LOCAL_PORT}" printf '\n%sExample connection:%s\n' "${BOLD}" "${RESET}" printf ' mongosh "mongodb://USERNAME@127.0.0.1:%s/?authSource=admin" --password\n' \ "${LOCAL_PORT}" printf '\n%sHelper commands:%s\n' "${BOLD}" "${RESET}" printf ' sudo mongo-tunnel status\n' printf ' sudo mongo-tunnel check\n' printf ' sudo mongo-tunnel start\n' printf ' sudo mongo-tunnel stop\n' printf ' sudo mongo-tunnel restart\n' printf ' sudo mongo-tunnel logs\n' printf ' mongo-tunnel connection\n' printf ' sudo mongo-tunnel uninstall\n' printf '\n%sSystemd commands:%s\n' "${BOLD}" "${RESET}" printf ' sudo systemctl status mongo-tunnel\n' printf ' sudo journalctl -u mongo-tunnel -f\n' printf '\n%sService status:%s ' "${BOLD}" "${RESET}" if systemctl is-active --quiet "${SERVICE_NAME}"; then printf '%sACTIVE AND RUNNING%s\n' "${GREEN}" "${RESET}" else printf '%sNOT RUNNING%s\n' "${RED}" "${RESET}" return 1 fi if [[ "${authentication_status}" == "success" ]]; then printf '%sCloudflare status:%s %sAUTHENTICATION CHECK PASSED%s\n' \ "${BOLD}" \ "${RESET}" \ "${GREEN}" \ "${RESET}" else printf '%sCloudflare status:%s %sAUTHENTICATION ERROR DETECTED%s\n' \ "${BOLD}" \ "${RESET}" \ "${YELLOW}" \ "${RESET}" fi printf '\n' } main() { require_root printf '\n%sMongoDB Cloudflare Tunnel Installer%s\n\n' \ "${BOLD}${CYAN}" "${RESET}" check_operating_system install_dependencies install_cloudflared local existing_token_id="" local existing_token_secret="" if [[ -f "${ENV_FILE}" ]]; then log_info "Existing installation detected. The credentials and configuration will be updated." existing_token_id="$( read_existing_value CF_ACCESS_CLIENT_ID || true )" existing_token_secret="$( read_existing_value CF_ACCESS_CLIENT_SECRET || true )" fi printf '\n%sAutomatic configuration:%s\n' "${BOLD}" "${RESET}" printf ' Cloudflare hostname: %s\n' "${CLOUDFLARE_HOSTNAME}" printf ' Local port: %s\n\n' "${LOCAL_PORT}" local token_id local token_secret token_id="$( prompt_value \ "Cloudflare Service Token ID" \ "${existing_token_id}" \ false )" token_secret="$( prompt_value \ "Cloudflare Service Token Secret" \ "${existing_token_secret}" \ true )" validate_credential "Service Token ID" "${token_id}" validate_credential "Service Token Secret" "${token_secret}" check_port_availability if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then log_info "Stopping the existing MongoDB tunnel..." systemctl stop "${SERVICE_NAME}" fi write_environment_file "${token_id}" "${token_secret}" write_systemd_service write_helper_cli systemctl daemon-reload systemctl enable "${SERVICE_NAME}" >/dev/null local service_start_time service_start_time="$(date '+%Y-%m-%d %H:%M:%S')" log_info "Starting MongoDB tunnel service..." systemctl restart "${SERVICE_NAME}" wait_for_listener local authentication_status="success" if ! check_cloudflare_authentication "${service_start_time}"; then authentication_status="failed" fi print_completion "${authentication_status}" if [[ "${authentication_status}" == "failed" ]]; then exit 2 fi } main "$@"