#!/bin/bash # ============================================================================ # 서비스 서버 전용 "로컬 Gitea" 설치 (v1.4.14, 요구 8) # # 목적: 사이트 생성·수정 버전(브랜치 sites//...)을 회사 소스 저장소(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"