Files
AriWeb-Suite/modules/webmaker/deploy/install-gitea.sh
T

134 lines
12 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# 서비스 서버 전용 "로컬 Gitea" 설치 (v1.4.14, 요구 8)
#
# 목적: 사이트 생성·수정 버전(브랜치 sites/<ID>/...)을 회사 소스 저장소(source.ibit.kr 의 OZ-Group/WebHosting_AI_agent)가
# 아니라 서비스 서버 안의 전용 Gitea 에 보관한다. 전용 계정(webmaker)과 비공개 저장소(webmaker/sites)를 만들고,
# 서비스가 그 계정의 SSH 키로 푸시하도록 systemd 환경 변수(SITES_GIT_REMOTE·SITES_GIT_WEB)를 바꾼다.
#
# 구성 (Docker, SQLite):
# 컨테이너 webmaker-gitea (gitea/gitea:1.27) 데이터 $DATA (기본 /srv/webmaker-gitea, 서비스 사용자 소유)
# HTTP $BIND:$HTTP_PORT (기본 LAN IP:3030 — 운영자가 브라우저로 버전을 볼 수 있게. 바깥에는 열지 않는다)
# SSH 127.0.0.1:$SSH_PORT (기본 2230 — 서비스가 같은 서버에서만 푸시하므로 로컬에만 연다)
# 가입 금지(DISABLE_REGISTRATION), 로그인해야 열람(REQUIRE_SIGNIN_VIEW), 저장소 기본 비공개
# 계정: webmaker-admin(관리자, 콘솔용) · webmaker(서비스 전용, 저장소 소유자) — 비밀번호는 $DATA/credentials.env (0600)
# SSH 키: ~/.ssh/id_ed25519_webmaker_gitea (없으면 생성) → webmaker 계정 키로 등록, ~/.ssh/config 에 Host webmaker-gitea
#
# 사용: deploy/install-gitea.sh [--bind IP] [--http-port 3030] [--ssh-port 2230] [--data /srv/webmaker-gitea]
# [--service webmaker] [--migrate-from <옛 원격 git 주소>] [--no-restart]
# 예) bear: deploy/install-gitea.sh --bind 192.168.20.23 --service webmaker --migrate-from git@source-webmaker:OZ-Group/WebHosting_AI_agent.git
# 66 : deploy/install-gitea.sh --bind 192.168.20.66 --service webhosting-agent --migrate-from git@source-webhosting:OZ-Group/WebHosting_AI_agent.git
# 전제: docker, 비밀번호 없는 sudo, curl, ssh-keygen. 다시 실행해도 안전하다(있는 것은 건너뜀).
# ============================================================================
set -euo pipefail
HERE=$(cd "$(dirname "$0")/.." && pwd)
BIND=""; HTTP_PORT=3030; SSH_PORT=2230; DATA=/srv/webmaker-gitea; SVC=""; MIGRATE=""; RESTART=1; IMAGE=gitea/gitea:1.27
while [ $# -gt 0 ]; do
case "$1" in
--bind) BIND=$2; shift 2;; --http-port) HTTP_PORT=$2; shift 2;; --ssh-port) SSH_PORT=$2; shift 2;; --data) DATA=$2; shift 2;;
--service) SVC=$2; shift 2;; --migrate-from) MIGRATE=$2; shift 2;; --no-restart) RESTART=0; shift;; --image) IMAGE=$2; shift 2;;
*) echo "알 수 없는 옵션: $1"; exit 1;;
esac
done
# 기본 바인드 주소: 첫 사설 IP (없으면 127.0.0.1). 운영자가 브라우저로 보려면 LAN IP 가 필요하다
[ -n "$BIND" ] || BIND=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)' | head -1); [ -n "$BIND" ] || BIND=127.0.0.1
# 서비스 이름: 이 서버에 있는 유닛을 찾는다
if [ -z "$SVC" ]; then for c in webmaker webhosting-agent; do systemctl list-unit-files "$c.service" 2>/dev/null | grep -q "$c.service" && { SVC=$c; break; }; done; fi
UNIT=/etc/systemd/system/${SVC:-webmaker}.service
ME=$(id -un); MY_UID=$(id -u); MY_GID=$(id -g); HOSTN=$(hostname)
echo "== 로컬 Gitea 설치: 컨테이너 webmaker-gitea · HTTP $BIND:$HTTP_PORT · SSH 127.0.0.1:$SSH_PORT · 데이터 $DATA · 서비스 ${SVC:-?}"
command -v docker >/dev/null || { echo "docker 가 없습니다 (apt install docker.io)"; exit 1; }
sudo -n true 2>/dev/null || { echo "비밀번호 없는 sudo 가 필요합니다"; exit 1; }
DOCKER="docker"; docker info >/dev/null 2>&1 || DOCKER="sudo -n docker"
# ---- 1. 컨테이너 ----
sudo -n mkdir -p "$DATA" && sudo -n chown "$MY_UID:$MY_GID" "$DATA" && chmod 750 "$DATA"
if $DOCKER ps -a --format '{{.Names}}' | grep -qx webmaker-gitea; then
echo "-- 컨테이너가 이미 있습니다. 실행 상태로 맞춥니다"; $DOCKER start webmaker-gitea >/dev/null 2>&1 || true
else
echo "-- 이미지 $IMAGE 받기·컨테이너 만들기"
$DOCKER pull -q "$IMAGE" >/dev/null
# INSTALL_LOCK=true: 웹 설치 화면 없이 바로 쓴다. SSH_DOMAIN/SSH_PORT 는 화면에 보이는 clone 주소용(실제 포트는 -p 로 연 $SSH_PORT)
$DOCKER run -d --name webmaker-gitea --restart unless-stopped \
-e USER_UID="$MY_UID" -e USER_GID="$MY_GID" \
-e GITEA__database__DB_TYPE=sqlite3 -e GITEA__database__PATH=/data/gitea/gitea.db \
-e GITEA__server__ROOT_URL="http://$BIND:$HTTP_PORT/" -e GITEA__server__DOMAIN="$BIND" -e GITEA__server__HTTP_PORT=3000 \
-e GITEA__server__SSH_DOMAIN=127.0.0.1 -e GITEA__server__SSH_PORT="$SSH_PORT" -e GITEA__server__SSH_LISTEN_PORT=22 \
-e GITEA__security__INSTALL_LOCK=true \
-e GITEA__service__DISABLE_REGISTRATION=true -e GITEA__service__REQUIRE_SIGNIN_VIEW=true -e GITEA__service__SHOW_REGISTRATION_BUTTON=false \
-e GITEA__repository__DEFAULT_PRIVATE=private -e GITEA__repository__DEFAULT_PUSH_CREATE_PRIVATE=true \
-e GITEA__openid__ENABLE_OPENID_SIGNIN=false -e GITEA__openid__ENABLE_OPENID_SIGNUP=false \
-e GITEA__webhook__ALLOWED_HOST_LIST=private -e GITEA__other__SHOW_FOOTER_VERSION=false \
-e GITEA__ui__DEFAULT_THEME=gitea-auto -e GITEA__i18n__LANGS=ko-KR,en-US -e GITEA__i18n__NAMES="한국어,English" \
-p "$BIND:$HTTP_PORT:3000" -p "127.0.0.1:$SSH_PORT:22" \
-v "$DATA:/data" -v /etc/timezone:/etc/timezone:ro -v /etc/localtime:/etc/localtime:ro \
"$IMAGE" >/dev/null
fi
# REQUIRE_SIGNIN_VIEW 때문에 /api/v1/version 은 403 — 로그인 없이 열리는 /api/healthz 로 준비를 확인한다
echo "-- 준비 대기"; for i in $(seq 1 60); do curl -fsS -m 3 "http://$BIND:$HTTP_PORT/api/healthz" >/dev/null 2>&1 && break; sleep 2; [ "$i" = 60 ] && { echo "Gitea 가 120초 안에 응답하지 않습니다: $($DOCKER logs --tail 20 webmaker-gitea 2>&1 | tail -5)"; exit 1; }; done
echo " gitea $($DOCKER exec -u git webmaker-gitea gitea --version 2>/dev/null | awk '{print $3}')"
# ---- 2. 계정 ----
CRED="$DATA/credentials.env"
if [ -f "$CRED" ]; then . "$CRED"; else
ADMIN_PW=$(openssl rand -base64 24 | tr -d '/+=' | cut -c1-24); WM_PW=$(openssl rand -base64 24 | tr -d '/+=' | cut -c1-24)
umask 077; printf 'ADMIN_USER=webmaker-admin\nADMIN_PW=%s\nWM_USER=webmaker\nWM_PW=%s\n' "$ADMIN_PW" "$WM_PW" > "$CRED"; umask 022
. "$CRED"
fi
gitea_cli() { $DOCKER exec -u git webmaker-gitea gitea "$@"; }
if gitea_cli admin user list 2>/dev/null | awk 'NR>1{print $2}' | grep -qx "$ADMIN_USER"; then echo "-- 관리자 계정 $ADMIN_USER 있음"; else
gitea_cli admin user create --admin --username "$ADMIN_USER" --password "$ADMIN_PW" --email "$ADMIN_USER@$HOSTN.local" --must-change-password=false >/dev/null && echo "-- 관리자 계정 $ADMIN_USER 생성"; fi
if gitea_cli admin user list 2>/dev/null | awk 'NR>1{print $2}' | grep -qx "$WM_USER"; then echo "-- 서비스 계정 $WM_USER 있음"; else
gitea_cli admin user create --username "$WM_USER" --password "$WM_PW" --email "$WM_USER@$HOSTN.local" --must-change-password=false >/dev/null && echo "-- 서비스 전용 계정 $WM_USER 생성"; fi
# ---- 3. 저장소 webmaker/sites ----
API="http://$BIND:$HTTP_PORT/api/v1"
code=$(curl -s -o /dev/null -w '%{http_code}' -u "$WM_USER:$WM_PW" "$API/repos/$WM_USER/sites")
if [ "$code" = "200" ]; then echo "-- 저장소 $WM_USER/sites 있음"; else
curl -fsS -u "$WM_USER:$WM_PW" -H 'Content-Type: application/json' -X POST "$API/user/repos" \
-d '{"name":"sites","private":true,"auto_init":false,"default_branch":"main","description":"AI Webmaker 사이트 소스 버전 (sites/<사이트ID>/main, sites/<사이트ID>/changes/NNNN-종류)"}' >/dev/null && echo "-- 저장소 $WM_USER/sites 생성 (비공개)"; fi
# ---- 4. SSH 키·설정 ----
KEY="$HOME/.ssh/id_ed25519_webmaker_gitea"; mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh"
[ -f "$KEY" ] || { ssh-keygen -q -t ed25519 -N '' -C "webmaker-service@$HOSTN" -f "$KEY"; echo "-- SSH 키 생성 $KEY"; }
PUB=$(cat "$KEY.pub")
if curl -fsS -u "$WM_USER:$WM_PW" "$API/user/keys" | grep -qF "$(echo "$PUB" | awk '{print $2}')"; then echo "-- 계정 키 등록됨"; else
curl -fsS -u "$WM_USER:$WM_PW" -H 'Content-Type: application/json' -X POST "$API/user/keys" -d "$(printf '{"title":"webmaker-service@%s","key":"%s","read_only":false}' "$HOSTN" "$PUB")" >/dev/null && echo "-- 공개키를 $WM_USER 계정에 등록"; fi
if ! grep -q '^Host webmaker-gitea$' "$HOME/.ssh/config" 2>/dev/null; then
printf '\n# AI Webmaker 로컬 Gitea (사이트 버전 보관, v1.4.14 deploy/install-gitea.sh)\nHost webmaker-gitea\n HostName 127.0.0.1\n Port %s\n User git\n IdentityFile %s\n IdentitiesOnly yes\n StrictHostKeyChecking accept-new\n' "$SSH_PORT" "$KEY" >> "$HOME/.ssh/config"; chmod 600 "$HOME/.ssh/config"; echo "-- ~/.ssh/config 에 Host webmaker-gitea 추가"
else
# 포트·키가 바뀌었으면 맞춘다
sed -i "/^Host webmaker-gitea$/,/^Host /{s#^ Port .*# Port $SSH_PORT#;s#^ IdentityFile .*# IdentityFile $KEY#}" "$HOME/.ssh/config"
fi
REMOTE="git@webmaker-gitea:$WM_USER/sites.git"; WEB="http://$BIND:$HTTP_PORT/$WM_USER/sites"
sleep 1; for i in 1 2 3 4 5; do GIT_SSH_COMMAND='ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new' git ls-remote "$REMOTE" >/dev/null 2>&1 && break; sleep 3; done
GIT_SSH_COMMAND='ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new' git ls-remote "$REMOTE" >/dev/null && echo "-- SSH 푸시 경로 확인: $REMOTE"
# ---- 5. 옛 저장소에서 사이트 브랜치 옮기기 ----
if [ -n "$MIGRATE" ]; then echo "== 마이그레이션 $MIGRATE → $REMOTE"; "$HERE/scripts/migrate-gitstore.sh" "$MIGRATE" "$REMOTE" --data-root "${DATA_ROOT:-$HERE/data}"; fi
# ---- 6. 웹메이커 설정 파일에 등록 (v1.5.7, 요구 6) ----
# 1.4.14~1.4.18 은 systemd 유닛의 Environment=SITES_GIT_REMOTE/WEB 을 고쳐 썼다. 이제 연동 정보는 설정 파일(data/config.json, source.*)에 두고
# 유닛의 옛 줄은 지운다(설정 파일이 우선이라 남아 있어도 무방하지만 두 곳에 있으면 헷갈린다). 서비스는 호출 때마다 설정을 읽으므로
# 푸시 주소는 재시작 없이도 다음 커밋부터 적용된다(--no-restart 로 재시작을 미룰 수 있다).
# API 토큰: 설정 페이지에서 상태 확인·저장소 만들기·공개키 등록을 API 로 하도록 webmaker 계정의 접근 토큰을 만들어 보관소에 넣는다(없을 때만).
CFGJS="$HERE/scripts/config.js"
[ -f "$HERE/data/config.json" ] || { [ -f "$UNIT" ] && (cd "$HERE" && node "$CFGJS" import-env --unit "$UNIT" >/dev/null) || true; }
HAS_TOKEN=$(cd "$HERE" && node -e "try{process.stdout.write(require('./lib/vault').secretOf('gitea')?'1':'')}catch(e){}")
TOKEN=""
if [ -z "$HAS_TOKEN" ]; then
TOKEN=$(curl -fsS -u "$WM_USER:$WM_PW" -H 'Content-Type: application/json' -X POST "$API/users/$WM_USER/tokens" -d "$(printf '{"name":"webmaker-service@%s-%s","scopes":["write:repository","write:user"]}' "$HOSTN" "$(date +%s)")" 2>/dev/null | sed -n 's/.*"sha1":"\([^"]*\)".*/\1/p' || true)
[ -n "$TOKEN" ] && echo "-- Gitea API 토큰 생성 ($WM_USER)" || echo "-- Gitea API 토큰을 만들지 못했습니다 (SSH 푸시만 사용, 설정 페이지에서 나중에 넣을 수 있음)"
fi
(cd "$HERE" && node "$CFGJS" set source.provider gitea source.remote "$REMOTE" source.web "$WEB" source.apiUrl "http://$BIND:$HTTP_PORT" source.user "$WM_USER" source.repo "$WM_USER/sites" ${SVC:+service.systemdUnit "$SVC"} ${TOKEN:+source.token "$TOKEN"})
echo "-- 설정 파일 $(cd "$HERE" && node "$CFGJS" path): source.remote=$REMOTE / source.web=$WEB / source.apiUrl=http://$BIND:$HTTP_PORT"
if [ -f "$UNIT" ] && sudo -n grep -qE '^Environment=SITES_GIT_(REMOTE|WEB|PREFIX)=' "$UNIT"; then
TMP=$(mktemp); sudo -n cat "$UNIT" | grep -vE '^Environment=SITES_GIT_(REMOTE|WEB|PREFIX)=' > "$TMP"; sudo -n install -m 644 "$TMP" "$UNIT"; rm -f "$TMP"; sudo -n systemctl daemon-reload
echo "-- $UNIT: 옛 SITES_GIT_* 환경 변수 제거 (설정 파일로 이전됨)"
fi
if [ "$RESTART" = 1 ] && [ -n "$SVC" ] && systemctl is-active --quiet "$SVC" 2>/dev/null; then
for i in $(seq 1 120); do pgrep -u "$ME" -f '[c]laude -p' >/dev/null 2>&1 && sleep 5 || break; done # 실행 중인 AI 작업이 끝날 때까지 기다린다
sudo -n systemctl restart "$SVC" && sleep 1 && echo "-- 서비스 $SVC 재시작: $(systemctl is-active "$SVC")"
fi
echo "== 완료. 웹: $WEB (계정 $ADMIN_USER / 비밀번호는 $CRED) · 서비스 푸시: $REMOTE"