Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
86 lines
7.1 KiB
Bash
Executable File
86 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 최초 1회 서버 준비(이후 인증서 발급 뒤에 다시 실행) — root 로 실행:
|
|
# sudo env SITE=heart-m SOURCE_BUNDLE=/tmp/arimail.bundle bash deploy/setup.sh
|
|
# SITE 는 deploy/sites/<SITE>.env 를 고릅니다(기본 arihakdang). 그 파일의 실행 계정(APP_USER)과 Node(NODE_BIN)는 미리 있어야 합니다.
|
|
# 새 사이트 파일은 deploy/new-site.sh 로 만듭니다(0.9.0). LOGIN_MODE(standalone | www)와 시스템 제한(LIMIT_*)은 그 파일에서 정하며,
|
|
# 환경 파일(gateway.env)은 처음 한 번만 만들므로 이후 바꾸려면 $ETC_DIR/gateway.env 를 직접 고치고 서비스를 재시작합니다.
|
|
# 하는 일: 디렉터리·사이트 설정 사본·환경 파일 → 저장소(번들 또는 원격) → systemd·sudoers → nginx(인증서 유무에 따라 80 전용/443) → certbot 훅 → 내장 Haraka(haraka-apply.sh, 1.0.0).
|
|
# 1.0.0: 운영 설정(Resend 키 등)은 환경 파일이 아니라 데이터 디렉터리의 config.json 에 있으며 배포 뒤 초기 설정 페이지에서 채웁니다(값이 없어도 배포는 성공).
|
|
set -euo pipefail
|
|
[ "$(id -u)" = 0 ] || { echo 'sudo 로 실행하세요' >&2; exit 1; }
|
|
. "$(dirname "$0")/lib.sh"
|
|
SITE="${SITE:-arihakdang}"
|
|
load_site "$DEPLOY_DIR/sites/$SITE.env"
|
|
echo "▶ 사이트: $SITE ($MAIL_HOST, 계정 $APP_USER, $APP_DIR, 로그인 $LOGIN_MODE)"
|
|
|
|
id "$APP_USER" >/dev/null 2>&1 || { echo "실행 계정 $APP_USER 이 없습니다. 계정을 먼저 만드세요(www 연동 사이트라면 www 배포 문서를 따르세요)." >&2; exit 1; }
|
|
[ -x "$NODE_BIN" ] || { echo "Node 실행 파일이 없습니다: $NODE_BIN — nodejs.org 의 Linux 바이너리(22.13+)를 풀어 두세요(예: tar -xJf node-v22.x-linux-x64.tar.xz -C $(dirname "$(dirname "$NODE_BIN")") --strip-components=1)." >&2; exit 1; }
|
|
MAJOR="$("$NODE_BIN" --version | sed -E 's/^v([0-9]+).*/\1/')"; [ "$MAJOR" -ge 22 ] || { echo "Node 22.13+ 필요: $("$NODE_BIN" --version)" >&2; exit 1; }
|
|
|
|
mkdir -p "$APP_DIR"/{repo,releases,data} "$ETC_DIR" "$ACME_WEBROOT"
|
|
chown -R "$APP_USER:$APP_USER" "$APP_DIR"
|
|
# 사이트 설정 사본: deploy.sh 와 tls-when-ready.sh 가 저장소 밖의 이 파일로 자기 사이트를 압니다.
|
|
install -o "$APP_USER" -g "$APP_USER" -m 644 "$DEPLOY_DIR/sites/$SITE.env" "$APP_DIR/site.env"
|
|
if [ ! -f "$ETC_DIR/gateway.env" ]; then
|
|
render "$DEPLOY_DIR/gateway.env.in" "$ETC_DIR/gateway.env"
|
|
chown "$APP_USER:$APP_USER" "$ETC_DIR/gateway.env"; chmod 600 "$ETC_DIR/gateway.env"
|
|
echo "→ $ETC_DIR/gateway.env 를 만들었습니다(배포에 필요한 값만). Resend 키 등 운영 설정은 배포 뒤 초기 설정 페이지에서 채웁니다."
|
|
fi
|
|
if [ ! -d "$APP_DIR/repo/.git" ]; then
|
|
if [ -n "${SOURCE_BUNDLE:-}" ]; then
|
|
echo '→ 번들에서 저장소를 만듭니다 (서버에 원격 자격 증명을 남기지 않음).'
|
|
sudo -u "$APP_USER" git clone -q -b main "$SOURCE_BUNDLE" "$APP_DIR/repo"
|
|
sudo -u "$APP_USER" git -C "$APP_DIR/repo" remote set-url origin "$REPO_URL"
|
|
else
|
|
echo "→ 저장소를 $APP_USER 계정으로 복제합니다 ($REPO_URL 자격 증명 필요)."
|
|
sudo -u "$APP_USER" git clone "$REPO_URL" "$APP_DIR/repo"
|
|
fi
|
|
fi
|
|
REPO_DEPLOY="$APP_DIR/repo/deploy"
|
|
render "$REPO_DEPLOY/systemd/mail-gateway.service.in" "/etc/systemd/system/$APP_NAME.service"; chmod 644 "/etc/systemd/system/$APP_NAME.service"
|
|
# 배포 스크립트(실행 계정)가 서비스 재시작과 nginx 재적재만 할 수 있게 합니다.
|
|
sudoers_line > "/etc/sudoers.d/$APP_NAME"; chmod 440 "/etc/sudoers.d/$APP_NAME"; visudo -c -q
|
|
# nginx: 인증서가 있으면 443, Cloudflare 오리진 인증서만 있으면 프록시용 443, 둘 다 없으면 80(ACME 챌린지)만 엽니다.
|
|
if [ -d "/etc/letsencrypt/live/$MAIL_HOST" ]; then
|
|
render "$REPO_DEPLOY/nginx/mail.conf.in" "/etc/nginx/sites-available/$MAIL_HOST.conf"
|
|
elif [ -n "$CF_CERT" ] && [ -f "$CF_CERT" ]; then
|
|
render "$REPO_DEPLOY/nginx/mail.cloudflare.conf.in" "/etc/nginx/sites-available/$MAIL_HOST.conf"
|
|
else
|
|
render "$REPO_DEPLOY/nginx/mail.http-only.conf.in" "/etc/nginx/sites-available/$MAIL_HOST.conf"
|
|
fi
|
|
chmod 644 "/etc/nginx/sites-available/$MAIL_HOST.conf"
|
|
ln -sfn "/etc/nginx/sites-available/$MAIL_HOST.conf" "/etc/nginx/sites-enabled/$MAIL_HOST.conf"
|
|
nginx -t && systemctl reload nginx
|
|
systemctl daemon-reload
|
|
systemctl enable -q "$APP_NAME"
|
|
# 메일 포트 TLS: certbot 의 개인키는 root 만 읽으므로, 실행 계정이 읽을 사본을 만들고 갱신 때마다 다시 복사합니다.
|
|
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
|
|
render "$REPO_DEPLOY/letsencrypt-deploy-hook.sh.in" "/etc/letsencrypt/renewal-hooks/deploy/$APP_NAME.sh"; chmod 755 "/etc/letsencrypt/renewal-hooks/deploy/$APP_NAME.sh"
|
|
if [ -d "/etc/letsencrypt/live/$MAIL_HOST" ]; then
|
|
RENEWED_LINEAGE="/etc/letsencrypt/live/$MAIL_HOST" SKIP_RESTART=1 sh "/etc/letsencrypt/renewal-hooks/deploy/$APP_NAME.sh"
|
|
sed -i -E "s#^POP_TLS_CERT=.*#POP_TLS_CERT=$ETC_DIR/tls/fullchain.pem#; s#^POP_TLS_KEY=.*#POP_TLS_KEY=$ETC_DIR/tls/privkey.pem#" "$ETC_DIR/gateway.env"
|
|
# 서비스가 이미 돌고 있으면 새 인증서로 다시 띄웁니다(처음 설치라면 아직 current 가 없어 deploy.sh 가 시작합니다).
|
|
if systemctl is-active --quiet "$APP_NAME"; then systemctl restart "$APP_NAME"; fi
|
|
else
|
|
# 인증서가 생길 때까지 15분마다 DNS 를 확인해 자동 발급하는 타이머를 켭니다(발급되면 스스로 꺼짐).
|
|
render "$REPO_DEPLOY/systemd/mail-tls.service.in" "/etc/systemd/system/$APP_NAME-tls.service"
|
|
render "$REPO_DEPLOY/systemd/mail-tls.timer.in" "/etc/systemd/system/$APP_NAME-tls.timer"
|
|
systemctl daemon-reload; systemctl enable -q --now "$APP_NAME-tls.timer"
|
|
cat <<MSG
|
|
→ TLS 인증서가 아직 없습니다. DNS($MAIL_HOST → 이 서버, Cloudflare 프록시 끔)가 바뀌면 $APP_NAME-tls.timer 가 15분 안에
|
|
certbot 으로 인증서를 받고 443 설정과 메일 포트 TLS(995/993/465)를 켭니다. 바로 하려면:
|
|
sudo bash $REPO_DEPLOY/tls-when-ready.sh $APP_DIR/site.env
|
|
MSG
|
|
fi
|
|
if [ -z "$WWW_DB_FILE" ]; then
|
|
cat <<MSG
|
|
→ 독립 실행(www 연동 없음)입니다. 첫 배포 때 서버가 초기 관리자 계정(${MAILBOX%@*} / adminpasswd)을 자동으로 만듭니다(0.8.24~).
|
|
https://$MAIL_HOST/admin 에 로그인해 바로 비밀번호를 바꾸세요. 추가 계정·비밀번호 재설정은 관리자 페이지 또는:
|
|
sudo -u $APP_USER env \$(grep -E '^(DB_FILE|MAILBOX)=' $ETC_DIR/gateway.env | xargs) $NODE_BIN $APP_DIR/current/server/user-cli.mjs passwd ${MAILBOX%@*}
|
|
MSG
|
|
fi
|
|
# 내장 Haraka(1.0.0): npm 설치와 관리 서비스 등록. 실패해도(오프라인) 설치는 계속되고 설정 화면이 안내합니다.
|
|
bash "$REPO_DEPLOY/haraka-apply.sh" "$APP_DIR/site.env" || echo "⚠️ Haraka 준비에 실패했습니다. 나중에 다시: sudo bash $REPO_DEPLOY/haraka-apply.sh $APP_DIR/site.env" >&2
|
|
echo "완료. 다음: sudo -u $APP_USER env SOURCE_BUNDLE=... $APP_DIR/repo/deploy/deploy.sh"
|
|
echo "배포가 끝나면 초기 설정 페이지 주소(포트 8791, 토큰 포함)가 출력됩니다. 방화벽을 쓰면 8791 포트를 열어 두세요(예: sudo ufw allow 8791/tcp)."
|