#!/usr/bin/env bash set -euo pipefail project_dir="${MAILHUB_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)}" data_dir="${MAILHUB_DATA_DIR:-${project_dir}/data}" fail() { echo "Dovecot preparation failed: $*" >&2 exit 1 } command -v openssl >/dev/null 2>&1 || fail "openssl is required." [[ ! -L "${data_dir}" ]] || fail "data directory must not be a symbolic link." mkdir -p "${data_dir}" data_dir="$(cd "${data_dir}" && pwd -P)" secret_file="${MAILHUB_DOVECOT_SECRET_FILE:-${data_dir}/secrets/dovecot_auth_secret}" webmail_secret_file="${MAILHUB_WEBMAIL_SSO_SECRET_FILE:-${data_dir}/secrets/webmail_sso_secret}" maildir_root="${MAILHUB_MAILDIR_ROOT:-${data_dir}/maildir}" host_os="$(uname -s)" host_uid="$(id -u)" webmail_reader_gid="${MAILHUB_WEBMAIL_SSO_READER_GID:-}" if [[ -z "${webmail_reader_gid}" && -f "${project_dir}/.env" ]]; then webmail_reader_gid_count="$(awk -F= ' $1 == "WEBMAIL_SSO_READER_GID" { count++ } END { print count + 0 } ' "${project_dir}/.env")" (( webmail_reader_gid_count <= 1 )) \ || fail "WEBMAIL_SSO_READER_GID must appear at most once in .env." webmail_reader_gid="$(awk -F= ' $1 == "WEBMAIL_SSO_READER_GID" { value = substr($0, index($0, "=") + 1) } END { print value } ' "${project_dir}/.env")" webmail_reader_gid="${webmail_reader_gid%$'\r'}" fi webmail_reader_gid="${webmail_reader_gid:-1000}" [[ "${webmail_reader_gid}" =~ ^[0-9]+$ ]] \ || fail "Webmail SSO reader gid must be a positive numeric gid." webmail_reader_gid="$((10#${webmail_reader_gid}))" (( webmail_reader_gid > 0 && webmail_reader_gid <= 2147483647 )) \ || fail "Webmail SSO reader gid must be a positive numeric gid." if [[ "${host_os}" == "Linux" && "${host_uid}" != "0" && "${host_uid}" != "1000" ]]; then fail "Linux preparation must run as root or host uid 1000 so the rootless containers can read and write their bind mounts." fi case "${secret_file}" in "${data_dir}"/*) ;; *) fail "secret file must stay inside the MailHub data directory." ;; esac case "${webmail_secret_file}" in "${data_dir}"/*) ;; *) fail "Webmail SSO secret file must stay inside the MailHub data directory." ;; esac case "${maildir_root}" in "${data_dir}"/*) ;; *) fail "Maildir root must stay inside the MailHub data directory." ;; esac [[ ! -L "${secret_file}" ]] || fail "secret file must not be a symbolic link." [[ ! -L "${webmail_secret_file}" ]] || fail "Webmail SSO secret file must not be a symbolic link." [[ ! -L "${maildir_root}" ]] || fail "Maildir root must not be a symbolic link." mkdir -p "$(dirname "${secret_file}")" "$(dirname "${webmail_secret_file}")" "${maildir_root}" secret_dir="$(dirname "${secret_file}")" webmail_secret_dir="$(dirname "${webmail_secret_file}")" prepare_secret() { local file="$1" local prefix="$2" local temporary_secret if [[ ! -f "${file}" ]]; then umask 077 temporary_secret="$(mktemp "$(dirname "${file}")/.${prefix}.XXXXXX")" if ! openssl rand -hex 32 >"${temporary_secret}"; then rm -f -- "${temporary_secret}" fail "unable to generate ${prefix} secret." fi chmod 0600 "${temporary_secret}" mv "${temporary_secret}" "${file}" fi [[ -f "${file}" ]] || fail "${prefix} secret path must be a regular file." local value value="$(tr -d '\r\n' <"${file}")" [[ "${value}" =~ ^[0-9a-fA-F]+$ ]] || fail "${prefix} secret must contain only hexadecimal characters." (( ${#value} >= 64 && ${#value} <= 512 )) || fail "${prefix} secret must contain 64-512 hexadecimal characters." } prepare_secret "${secret_file}" "dovecot-auth" prepare_secret "${webmail_secret_file}" "webmail-sso" if [[ "${host_uid}" == "0" ]]; then chown 1000:1000 \ "${secret_dir}" \ "${secret_file}" \ "${maildir_root}" # MailHub remains the owner. The dedicated reader group allows the actual # Roundcube PHP worker gid to read the shared secret without world access. chown "1000:${webmail_reader_gid}" \ "${webmail_secret_dir}" \ "${webmail_secret_file}" fi chmod 0700 "${maildir_root}" chmod 0750 "${secret_dir}" "${webmail_secret_dir}" chmod 0400 "${secret_file}" chmod 0440 "${webmail_secret_file}" if [[ "${host_os}" == "Linux" ]]; then [[ "$(stat -c '%g' "${webmail_secret_dir}")" == "${webmail_reader_gid}" \ && "$(stat -c '%g' "${webmail_secret_file}")" == "${webmail_reader_gid}" ]] \ || fail "Webmail SSO secret reader gid could not be applied; run preparation as root." fi echo "Dovecot storage and authentication secrets are ready."