#!/usr/bin/env bash # 최초 1회 서버 준비(이후 인증서 발급 뒤에 다시 실행) — root 로 실행: # sudo env SITE=heart-m SOURCE_BUNDLE=/tmp/arimail.bundle bash deploy/setup.sh # SITE 는 deploy/sites/.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 <