prepare-dovecot.sh 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. project_dir="${MAILHUB_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)}"
  4. data_dir="${MAILHUB_DATA_DIR:-${project_dir}/data}"
  5. fail() {
  6. echo "Dovecot preparation failed: $*" >&2
  7. exit 1
  8. }
  9. command -v openssl >/dev/null 2>&1 || fail "openssl is required."
  10. [[ ! -L "${data_dir}" ]] || fail "data directory must not be a symbolic link."
  11. mkdir -p "${data_dir}"
  12. data_dir="$(cd "${data_dir}" && pwd -P)"
  13. secret_file="${MAILHUB_DOVECOT_SECRET_FILE:-${data_dir}/secrets/dovecot_auth_secret}"
  14. webmail_secret_file="${MAILHUB_WEBMAIL_SSO_SECRET_FILE:-${data_dir}/secrets/webmail_sso_secret}"
  15. maildir_root="${MAILHUB_MAILDIR_ROOT:-${data_dir}/maildir}"
  16. host_os="$(uname -s)"
  17. host_uid="$(id -u)"
  18. webmail_reader_gid="${MAILHUB_WEBMAIL_SSO_READER_GID:-}"
  19. if [[ -z "${webmail_reader_gid}" && -f "${project_dir}/.env" ]]; then
  20. webmail_reader_gid_count="$(awk -F= '
  21. $1 == "WEBMAIL_SSO_READER_GID" { count++ }
  22. END { print count + 0 }
  23. ' "${project_dir}/.env")"
  24. (( webmail_reader_gid_count <= 1 )) \
  25. || fail "WEBMAIL_SSO_READER_GID must appear at most once in .env."
  26. webmail_reader_gid="$(awk -F= '
  27. $1 == "WEBMAIL_SSO_READER_GID" { value = substr($0, index($0, "=") + 1) }
  28. END { print value }
  29. ' "${project_dir}/.env")"
  30. webmail_reader_gid="${webmail_reader_gid%$'\r'}"
  31. fi
  32. webmail_reader_gid="${webmail_reader_gid:-1000}"
  33. [[ "${webmail_reader_gid}" =~ ^[0-9]+$ ]] \
  34. || fail "Webmail SSO reader gid must be a positive numeric gid."
  35. webmail_reader_gid="$((10#${webmail_reader_gid}))"
  36. (( webmail_reader_gid > 0 && webmail_reader_gid <= 2147483647 )) \
  37. || fail "Webmail SSO reader gid must be a positive numeric gid."
  38. if [[ "${host_os}" == "Linux" && "${host_uid}" != "0" && "${host_uid}" != "1000" ]]; then
  39. fail "Linux preparation must run as root or host uid 1000 so the rootless containers can read and write their bind mounts."
  40. fi
  41. case "${secret_file}" in
  42. "${data_dir}"/*) ;;
  43. *) fail "secret file must stay inside the MailHub data directory." ;;
  44. esac
  45. case "${webmail_secret_file}" in
  46. "${data_dir}"/*) ;;
  47. *) fail "Webmail SSO secret file must stay inside the MailHub data directory." ;;
  48. esac
  49. case "${maildir_root}" in
  50. "${data_dir}"/*) ;;
  51. *) fail "Maildir root must stay inside the MailHub data directory." ;;
  52. esac
  53. [[ ! -L "${secret_file}" ]] || fail "secret file must not be a symbolic link."
  54. [[ ! -L "${webmail_secret_file}" ]] || fail "Webmail SSO secret file must not be a symbolic link."
  55. [[ ! -L "${maildir_root}" ]] || fail "Maildir root must not be a symbolic link."
  56. mkdir -p "$(dirname "${secret_file}")" "$(dirname "${webmail_secret_file}")" "${maildir_root}"
  57. secret_dir="$(dirname "${secret_file}")"
  58. webmail_secret_dir="$(dirname "${webmail_secret_file}")"
  59. prepare_secret() {
  60. local file="$1"
  61. local prefix="$2"
  62. local temporary_secret
  63. if [[ ! -f "${file}" ]]; then
  64. umask 077
  65. temporary_secret="$(mktemp "$(dirname "${file}")/.${prefix}.XXXXXX")"
  66. if ! openssl rand -hex 32 >"${temporary_secret}"; then
  67. rm -f -- "${temporary_secret}"
  68. fail "unable to generate ${prefix} secret."
  69. fi
  70. chmod 0600 "${temporary_secret}"
  71. mv "${temporary_secret}" "${file}"
  72. fi
  73. [[ -f "${file}" ]] || fail "${prefix} secret path must be a regular file."
  74. local value
  75. value="$(tr -d '\r\n' <"${file}")"
  76. [[ "${value}" =~ ^[0-9a-fA-F]+$ ]] || fail "${prefix} secret must contain only hexadecimal characters."
  77. (( ${#value} >= 64 && ${#value} <= 512 )) || fail "${prefix} secret must contain 64-512 hexadecimal characters."
  78. }
  79. prepare_secret "${secret_file}" "dovecot-auth"
  80. prepare_secret "${webmail_secret_file}" "webmail-sso"
  81. if [[ "${host_uid}" == "0" ]]; then
  82. chown 1000:1000 \
  83. "${secret_dir}" \
  84. "${secret_file}" \
  85. "${maildir_root}"
  86. # MailHub remains the owner. The dedicated reader group allows the actual
  87. # Roundcube PHP worker gid to read the shared secret without world access.
  88. chown "1000:${webmail_reader_gid}" \
  89. "${webmail_secret_dir}" \
  90. "${webmail_secret_file}"
  91. fi
  92. chmod 0700 "${maildir_root}"
  93. chmod 0750 "${secret_dir}" "${webmail_secret_dir}"
  94. chmod 0400 "${secret_file}"
  95. chmod 0440 "${webmail_secret_file}"
  96. if [[ "${host_os}" == "Linux" ]]; then
  97. [[ "$(stat -c '%g' "${webmail_secret_dir}")" == "${webmail_reader_gid}" \
  98. && "$(stat -c '%g' "${webmail_secret_file}")" == "${webmail_reader_gid}" ]] \
  99. || fail "Webmail SSO secret reader gid could not be applied; run preparation as root."
  100. fi
  101. echo "Dovecot storage and authentication secrets are ready."