실제 환경 배포 결함 수정: Agent 실행 계정 모듈 설치(module-as-user.sh), AriMail 마이그레이션 DB 경로, 도입 모듈 REVISION 버전, 기존 유닛 보존, 포털 nginx 접근 경로
- Agent: 유닛 User 가 Agent 계정과 다르면 deploy/module-as-user.sh 로 그 계정이 풀기·전환·DB 백업·마이그레이션 수행(sudoers 최소 규칙) - AriMail 마이그레이션에 유닛 EnvironmentFile(DB_FILE) 적용 - 도입 모듈 실제 버전은 REVISION.json 의 Suite 버전, 상류 버전은 upstreamVersion - Webmaker: keepExistingUnit 으로 운영 유닛의 강화 지시문 보존 - bootstrap-hal.sh: my.ariweb.net nginx 접근 경로 설치 - test/agent.test.mjs 회귀 테스트, VERSION_HISTORY·RUNBOOK 갱신, 로컬 E2E 보고서(릴리스 빌드 실행분) 반영 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
45b591b18c
commit
00c42abca1
@@ -37,10 +37,14 @@ echo "== 3/6 systemd 유닛 ariweb-hal"
|
||||
sed -e "s#{{APP_DIR}}#$APP_DIR#g" -e "s#{{USER}}#$USER_NAME#g" -e "s#{{NODE}}#$NODE_BIN#g" -e "s#{{VERSION}}#$VERSION#g" deploy/units/ariweb-hal.service.tmpl | sudo -n tee /etc/systemd/system/ariweb-hal.service >/dev/null
|
||||
sudo -n install -m 440 deploy/sudoers/ariweb-agent /etc/sudoers.d/ariweb-agent; sudo -n visudo -c >/dev/null
|
||||
sudo -n systemctl daemon-reload; sudo -n systemctl enable --now ariweb-hal >/dev/null; sleep 2; systemctl is-active --quiet ariweb-hal || { echo "ariweb-hal 기동 실패 — journalctl -u ariweb-hal" >&2; exit 1; }
|
||||
echo "== 4/6 nginx $HOST (인증서 $CERT)"
|
||||
echo "== 4/6 nginx $HOST · $PORTAL_HOST (인증서 $CERT)"
|
||||
[ -f "$CERT/fullchain.pem" ] || { echo "인증서 $CERT/fullchain.pem 이 없습니다" >&2; exit 1; }
|
||||
sed -e "s#{{HAL_HOST}}#$HOST#g" -e "s#{{CERT_DIR}}#$CERT#g" -e "s#{{PORT}}#8900#g" -e "s#{{ACME_ROOT}}#$ACME#g" deploy/nginx/ariweb-hal.conf.tmpl | sudo -n tee /etc/nginx/sites-available/ariweb-hal.conf >/dev/null
|
||||
sudo -n ln -sf /etc/nginx/sites-available/ariweb-hal.conf /etc/nginx/sites-enabled/ariweb-hal.conf; sudo -n cp deploy/nginx/awstats.conf /etc/nginx/conf.d/awstats.conf; sudo -n nginx -t && sudo -n systemctl reload nginx
|
||||
sudo -n ln -sf /etc/nginx/sites-available/ariweb-hal.conf /etc/nginx/sites-enabled/ariweb-hal.conf; sudo -n cp deploy/nginx/awstats.conf /etc/nginx/conf.d/awstats.conf
|
||||
# 포털 접근 경로(my.ariweb.net → 127.0.0.1:8910)도 여기서 준비한다 — 포털 자체는 HAL 작업으로 설치되지만 nginx 접근 경로는 "접근 경로 준비"(§32.2 2단계)에 속한다(실제 환경 결함 수정: 이전에는 어디서도 만들지 않았다).
|
||||
sed -e "s#{{PORTAL_HOST}}#$PORTAL_HOST#g" -e "s#{{CERT_DIR}}#$CERT#g" -e "s#{{PORT}}#8910#g" -e "s#{{ACME_ROOT}}#$ACME#g" deploy/nginx/ariweb-portal.conf.tmpl | sudo -n tee /etc/nginx/sites-available/ariweb-portal.conf >/dev/null
|
||||
sudo -n ln -sf /etc/nginx/sites-available/ariweb-portal.conf /etc/nginx/sites-enabled/ariweb-portal.conf
|
||||
sudo -n nginx -t && sudo -n systemctl reload nginx
|
||||
echo "== 5/6 HAL 자체 건강 확인"
|
||||
curl -fsS http://127.0.0.1:8900/api/health | $NODE_BIN -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>{const h=JSON.parse(s);console.log(' HAL',h.version,'db',h.db,'jobs',h.jobs,'bootstrap',JSON.stringify(h.bootstrap))})"
|
||||
curl -fsS --resolve "$HOST:443:127.0.0.1" "https://$HOST/api/health" >/dev/null && echo " https://$HOST/api/health OK (로컬 resolve)" || echo " 경고: https://$HOST 확인 실패 — DNS A 레코드($HOST → 이 서버)와 인증서를 확인하세요"
|
||||
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# deploy/module-as-user.sh — 도입 모듈을 "그 모듈의 실행 계정" 으로 설치하기 위한 최소 파일 작업 도우미(§32).
|
||||
#
|
||||
# 왜 — Node Agent 는 master 계정으로 돌지만 도입 모듈(AriMail 등)은 서버에 이미 다른 계정(arihakdang 등)으로 설치되어 있고
|
||||
# 그 디렉터리(/srv/<모듈>/releases, data)는 그 계정만 쓸 수 있다. Agent 가 직접 쓰면 EACCES 로 끝나므로(실제 환경 검증에서 발견),
|
||||
# sudoers(deploy/sudoers/ariweb-agent)에 "이 스크립트만 root 가 아닌 임의 계정으로" 허용하고 Agent 가 `sudo -n -u <계정> bash <이 파일> <하위 명령>` 으로 부른다.
|
||||
# 하위 명령은 모듈 배치에 필요한 것만 두고, 모든 경로 인자는 --app-dir 아래(또는 명시한 artifact)로 제한한다. 비밀값은 다루지 않고 출력하지 않는다.
|
||||
#
|
||||
# 사용: module-as-user.sh <하위 명령> --app-dir <모듈 디렉터리> [인자…]
|
||||
# extract --tgz <artifact.tar.gz> --dest <appDir/releases/<ver>> 릴리스 디렉터리를 비우고 artifact 를 푼다
|
||||
# write --file <appDir 아래 파일> 표준 입력을 파일로 쓴다(REVISION.json·.previous.json)
|
||||
# switch --link <appDir/current> --target <릴리스 디렉터리> 심링크 원자 교체(ln → mv -T)
|
||||
# remove --dir <appDir/releases/<old>> 오래된 릴리스 삭제(releases 아래만)
|
||||
# backup-db --db <sqlite 파일> --out <백업 파일> [--node <node>] SQLite 온라인 백업(VACUUM INTO)
|
||||
# migrate --cwd <릴리스 디렉터리> --script <상대 경로> [--env-file <f>] [--node <node>] EnvironmentFile 을 읽어 마이그레이션 실행
|
||||
set -euo pipefail
|
||||
SUB="${1:-}"; shift || true
|
||||
APP_DIR=""; TGZ=""; DEST=""; FILE=""; LINK=""; TARGET=""; DIR=""; DB=""; OUT=""; NODE_BIN=""; CWD=""; SCRIPT=""; ENV_FILE=""
|
||||
while [ $# -gt 0 ]; do case "$1" in
|
||||
--app-dir) APP_DIR="$2"; shift 2;; --tgz) TGZ="$2"; shift 2;; --dest) DEST="$2"; shift 2;; --file) FILE="$2"; shift 2;; --link) LINK="$2"; shift 2;; --target) TARGET="$2"; shift 2;;
|
||||
--dir) DIR="$2"; shift 2;; --db) DB="$2"; shift 2;; --out) OUT="$2"; shift 2;; --node) NODE_BIN="$2"; shift 2;; --cwd) CWD="$2"; shift 2;; --script) SCRIPT="$2"; shift 2;; --env-file) ENV_FILE="$2"; shift 2;;
|
||||
*) echo "알 수 없는 인자 $1" >&2; exit 2;; esac; done
|
||||
[ -n "$APP_DIR" ] && [ -d "$APP_DIR" ] || { echo "--app-dir <존재하는 디렉터리> 가 필요합니다" >&2; exit 2; }
|
||||
APP_DIR="$(cd "$APP_DIR" && pwd -P)"
|
||||
# 경로가 appDir 아래인지(심링크를 따라간 실제 경로 기준, 아직 없는 경로는 부모 기준) 확인한다 — 다른 디렉터리를 건드리지 못하게.
|
||||
inside() { local p="$1" d real; d="$(dirname "$p")"; while [ ! -d "$d" ] && [ "$d" != "/" ]; do d="$(dirname "$d")"; done; real="$(cd "$d" && pwd -P)"; case "$real" in "$APP_DIR"|"$APP_DIR"/*) return 0;; esac; echo "경로가 모듈 디렉터리($APP_DIR) 밖입니다: $p" >&2; exit 3; }
|
||||
[ -n "$NODE_BIN" ] || NODE_BIN="$(command -v node || true)"
|
||||
case "$SUB" in
|
||||
extract)
|
||||
[ -f "$TGZ" ] || { echo "artifact 가 없습니다: $TGZ" >&2; exit 2; }; inside "$DEST"; case "$DEST" in "$APP_DIR"/releases/*) ;; *) echo "--dest 는 releases 아래여야 합니다" >&2; exit 3;; esac
|
||||
rm -rf "$DEST"; mkdir -p "$DEST"; tar -xzf "$TGZ" -C "$DEST"; echo "extracted $DEST";;
|
||||
write)
|
||||
inside "$FILE"; mkdir -p "$(dirname "$FILE")"; tmp="$FILE.tmp-$$"; cat > "$tmp"; mv -f "$tmp" "$FILE"; echo "written $FILE";;
|
||||
switch)
|
||||
inside "$LINK"; inside "$TARGET"; [ -d "$TARGET" ] || { echo "대상 디렉터리가 없습니다: $TARGET" >&2; exit 2; }
|
||||
ln -sfn "$TARGET" "$LINK.tmp-$$"; mv -Tf "$LINK.tmp-$$" "$LINK"; echo "switched $LINK -> $TARGET";;
|
||||
remove)
|
||||
inside "$DIR"; case "$DIR" in "$APP_DIR"/releases/*) ;; *) echo "--dir 는 releases 아래여야 합니다" >&2; exit 3;; esac; rm -rf "$DIR"; echo "removed $DIR";;
|
||||
backup-db)
|
||||
inside "$OUT"; [ -f "$DB" ] || { echo "skipped no_db"; exit 0; }; mkdir -p "$(dirname "$OUT")"
|
||||
# node:sqlite 의 VACUUM INTO 로 WAL 포함 일관된 사본을 만든다(원본 deploy.sh 의 python backup 과 같은 목적).
|
||||
"$NODE_BIN" --no-warnings -e "const {DatabaseSync}=require('node:sqlite');const d=new DatabaseSync(process.argv[1],{readOnly:true});try{d.exec(\"VACUUM INTO '\"+process.argv[2].replace(/'/g,\"''\")+\"'\")}finally{d.close()}" "$DB" "$OUT"; echo "backup $OUT";;
|
||||
migrate)
|
||||
inside "$CWD"; [ -f "$CWD/$SCRIPT" ] || { echo "skipped no_migrate"; exit 0; }
|
||||
# systemd 유닛의 EnvironmentFile(DB_FILE·DATA_DIR 등)을 같은 계정으로 읽어 환경에 싣는다 — 없으면 모듈 기본 경로(릴리스 디렉터리 안)를 쓰게 되어 운영 DB 가 아닌 빈 DB 를 만드는 문제를 막는다.
|
||||
if [ -n "$ENV_FILE" ]; then [ -r "$ENV_FILE" ] || { echo "EnvironmentFile 을 읽을 수 없습니다: $ENV_FILE" >&2; exit 2; }; set -a; . "$ENV_FILE"; set +a; fi
|
||||
cd "$CWD"; exec "$NODE_BIN" --no-warnings "$SCRIPT";;
|
||||
*) echo "알 수 없는 하위 명령: $SUB" >&2; exit 2;;
|
||||
esac
|
||||
@@ -7,3 +7,6 @@ master ALL=(root) NOPASSWD: /usr/bin/tee /etc/nginx/sites-available/aw-*, /usr/b
|
||||
master ALL=(root) NOPASSWD: /usr/bin/ln -sf /etc/nginx/sites-available/aw-* /etc/nginx/sites-enabled/*, /usr/bin/ln -sf /etc/nginx/sites-available/ariweb-* /etc/nginx/sites-enabled/*, /usr/bin/rm -f /etc/nginx/sites-enabled/aw-*, /usr/bin/rm -f /etc/nginx/sites-available/aw-*, /usr/bin/rm -f /etc/systemd/system/ariweb-site-*
|
||||
master ALL=(root) NOPASSWD: /usr/bin/mkdir -p /etc/nginx/*, /usr/bin/mkdir -p /var/www/*, /usr/bin/chown master\:master /var/www/*, /usr/bin/chmod * /etc/nginx/sites-available/aw-*, /usr/bin/chmod * /etc/systemd/system/ariweb-*, /usr/bin/chmod * /etc/nginx/conf.d/awstats.conf
|
||||
master ALL=(root) NOPASSWD: /usr/bin/true
|
||||
# 실행 계정이 다른 도입 모듈(AriMail 의 arihakdang 등) 설치: 이 도우미 스크립트만 root 가 아닌 계정으로 허용(agent/jobs/module.mjs 실행 계정 모드)
|
||||
Runas_Alias ARIWEB_SVC = ALL, !root
|
||||
master ALL=(ARIWEB_SVC) NOPASSWD: /usr/bin/bash /srv/ariweb/agent/current/deploy/module-as-user.sh *
|
||||
|
||||
Reference in New Issue
Block a user