feat(cicd): fix webhook server, git URLs, push Jenkinsfiles to all 5 Gitea repos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DESKTOP-TKLFCPR\ython 2026-06-01 19:55:19 +09:00
parent 28d3ba4836
commit 5561d0d050
7 changed files with 460 additions and 0 deletions

View File

@ -0,0 +1,27 @@
"""5개 repo Jenkinsfile → Gitea push"""
import subprocess, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
REPOS = ["zioinfo-web", "guardia-itsm", "guardia-manager", "guardia-messenger", "guardia-docs"]
BASE = "C:/GUARDiA/repos"
GITEA = "http://zio:Zio%40Admin2026%21@101.79.17.164:3000/zio"
ok, fail = [], []
for repo in REPOS:
path = f"{BASE}/{repo}"
# remote 설정
subprocess.run(['git', '-C', path, 'remote', 'remove', 'origin'],
capture_output=True)
subprocess.run(['git', '-C', path, 'remote', 'add', 'origin', f"{GITEA}/{repo}.git"],
capture_output=True)
# push
r = subprocess.run(['git', '-C', path, 'push', '-u', 'origin', 'main', '--force'],
capture_output=True, text=True, encoding='utf-8', errors='replace')
if r.returncode == 0:
print(f"{repo} push 완료")
ok.append(repo)
else:
print(f"{repo} push 실패: {r.stderr[:200]}")
fail.append(repo)
print(f"\n완료: {len(ok)}/5 실패: {fail}")

View File

@ -0,0 +1,88 @@
"""Gitea Contents API로 Jenkinsfile 직접 업로드"""
import paramiko, sys, base64, json
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('101.79.17.164', username='root', password='1q2w3e!Q', timeout=15)
def run(label, cmd, timeout=30):
print(f'[{label}]')
_, o, e = client.exec_command(cmd, timeout=timeout)
out = o.read().decode('utf-8', 'replace').strip()
if out: print(f' {out[:400]}')
return out
# Gitea API: bearer token 방식으로 처리 (@ 특수문자 문제 우회)
# 먼저 API token 확인 또는 생성
run('Gitea API 토큰 확인', """
curl -sf 'http://127.0.0.1:9003/api/v1/users/zio/tokens' \
--header 'Authorization: Basic '"$(echo -n 'zio:Zio@Admin2026!' | base64)" \
2>/dev/null | python3 -c "import sys,json; [print(t['name'], t['id']) for t in json.load(sys.stdin)]" 2>/dev/null || echo FAIL
""")
# API 토큰 생성
run('Gitea API 토큰 생성', """
curl -sf -X POST 'http://127.0.0.1:9003/api/v1/users/zio/tokens' \
--header 'Authorization: Basic '"$(echo -n 'zio:Zio@Admin2026!' | base64)" \
--header 'Content-Type: application/json' \
-d '{"name":"ci-deploy-token-2026","scopes":["write:repository","read:repository"]}' \
2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print('token:', d.get('sha1','ERROR'))" 2>/dev/null || echo FAIL
""")
def rf(p): return open(p, encoding='utf-8').read()
JENKINSFILES = {
"guardia-manager": rf('C:/GUARDiA/workspace/guardia-manager/Jenkinsfile'),
"guardia-messenger":rf('C:/GUARDiA/workspace/guardia-messenger/Jenkinsfile'),
"guardia-docs": rf('C:/GUARDiA/workspace/guardia-docs/Jenkinsfile'),
"zioinfo-web": rf('C:/GUARDiA/workspace/zioinfo-web/Jenkinsfile'),
"guardia-itsm": rf('C:/GUARDiA/workspace/guardia-itsm/Jenkinsfile'),
}
print('\n[Gitea Contents API로 Jenkinsfile 업로드]')
for repo, content in JENKINSFILES.items():
b64 = base64.b64encode(content.encode('utf-8')).decode()
# 기존 파일 SHA 확인
sha_cmd = f"""curl -sf 'http://127.0.0.1:9003/api/v1/repos/zio/{repo}/contents/Jenkinsfile' \
--header 'Authorization: Basic '"$(echo -n 'zio:Zio@Admin2026!' | base64)" \
2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null"""
_, o, _ = client.exec_command(sha_cmd, timeout=10)
sha = o.read().decode('utf-8', 'replace').strip()
if sha:
# 파일 업데이트
payload = json.dumps({
"message": "ci: update Jenkinsfile for CI/CD pipeline",
"content": b64,
"sha": sha,
"branch": "main"
})
method = "PUT"
print(f' {repo}: 기존 파일 업데이트 (sha={sha[:8]}...)')
else:
# 파일 신규 생성
payload = json.dumps({
"message": "ci: add Jenkinsfile for CI/CD pipeline",
"content": b64,
"branch": "main"
})
method = "POST"
print(f' {repo}: 신규 파일 생성')
# payload를 임시 파일로 저장
sftp = client.open_sftp()
with sftp.open(f'/tmp/jf_{repo}.json', 'w') as f:
f.write(payload)
sftp.close()
run(f'upload {repo}', f"""
curl -sf -X {method} 'http://127.0.0.1:9003/api/v1/repos/zio/{repo}/contents/Jenkinsfile' \
--header 'Authorization: Basic '"$(echo -n 'zio:Zio@Admin2026!' | base64)" \
--header 'Content-Type: application/json' \
--data @/tmp/jf_{repo}.json \
2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print('OK:', d.get('content',{{}}).get('name','?'))" 2>/dev/null || echo FAIL
rm -f /tmp/jf_{repo}.json
""")
client.close()
print('\n=== Jenkinsfile 업로드 완료 ===')

View File

@ -0,0 +1,151 @@
"""서버에서 직접 각 Gitea repo에 Jenkinsfile 추가 push"""
import paramiko, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('101.79.17.164', username='root', password='1q2w3e!Q', timeout=15)
sftp = client.open_sftp()
def run(label, cmd, timeout=60):
print(f' [{label}]')
_, o, e = client.exec_command(cmd, timeout=timeout)
out = o.read().decode('utf-8', 'replace').strip()
err = e.read().decode('utf-8', 'replace').strip()
if out: print(f' {out[:300]}')
if err:
bad = [l for l in err.splitlines() if not any(k in l.lower() for k in ['warn','hint','note','already'])]
if bad: print(f' ERR: {bad[-1][:200]}')
return out
GITEA = "http://zio:Zio%40Admin2026%21@localhost:3000/zio"
# Jenkinsfile 내용 정의
JENKINSFILES = {
"guardia-manager": """pipeline {
agent any
environment {
STATIC = '/var/www/manager'
NOTIFY = "${ITSM_BASE_URL}/api/messenger/webhook"
}
options { buildDiscarder(logRotator(numToKeepStr: '5')); timeout(time: 15, unit: 'MINUTES') }
stages {
stage('Checkout') { steps { checkout scm } }
stage('Build') {
steps {
dir('frontend') {
sh 'npm ci 2>/dev/null || npm install'
sh 'npm run build'
}
}
}
stage('Deploy') {
when { branch 'main' }
steps {
sh \"\"\"
cp -r frontend/dist/. ${STATIC}/
systemctl restart guardia-manager 2>/dev/null || true
\"\"\"
}
}
}
post {
success { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\\"event\\\":\\\"build_result\\\",\\\"room\\\":\\\"ops\\\",\\\"success\\\":true,\\\"result_summary\\\":\\\"✅ guardia-manager 배포 완료\\\"}' 2>/dev/null || true" }
failure { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\\"event\\\":\\\"build_result\\\",\\\"room\\\":\\\"ops\\\",\\\"success\\\":false,\\\"result_summary\\\":\\\"❌ guardia-manager 빌드 실패\\\"}' 2>/dev/null || true" }
}
}""",
"guardia-messenger": """pipeline {
agent any
environment {
NOTIFY = "${ITSM_BASE_URL}/api/messenger/webhook"
}
options {
buildDiscarder(logRotator(numToKeepStr: '5'))
timeout(time: 10, unit: 'MINUTES')
timestamps()
}
stages {
stage('Checkout') { steps { checkout scm } }
stage('Validate') {
steps {
sh 'node --version || true'
sh "grep -E '\\\"expo\\\"' package.json | head -1 || true"
}
}
stage('Install') {
steps { sh 'npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps' }
}
stage('Type Check') {
when { expression { fileExists('tsconfig.json') } }
steps { sh 'npx tsc --noEmit 2>/dev/null || true' }
}
stage('EAS Build Trigger') {
when {
allOf {
branch 'main'
expression { return env.EXPO_TOKEN?.trim() }
}
}
steps {
sh '''
npm install -g @expo/eas-cli 2>/dev/null || true
eas build --platform android --non-interactive --no-wait --profile production 2>/dev/null || echo "EAS 빌드 큐 등록됨"
'''
}
}
}
post {
success { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\\"event\\\":\\\"build_result\\\",\\\"room\\\":\\\"ops\\\",\\\"success\\\":true,\\\"result_summary\\\":\\\"✅ guardia-messenger 검증 완료 #${BUILD_NUMBER}\\\"}' 2>/dev/null || true" }
failure { sh "curl -sf -X POST ${NOTIFY} -H 'Content-Type:application/json' -d '{\\\"event\\\":\\\"build_result\\\",\\\"room\\\":\\\"ops\\\",\\\"success\\\":false,\\\"result_summary\\\":\\\"❌ guardia-messenger 빌드 실패 #${BUILD_NUMBER}\\\"}' 2>/dev/null || true" }
}
}""",
"guardia-docs": """pipeline {
agent any
environment { DOCS = '/var/www/docs' }
options { buildDiscarder(logRotator(numToKeepStr: '3')) }
stages {
stage('Checkout') { steps { checkout scm } }
stage('Deploy') {
when { branch 'main' }
steps { sh 'mkdir -p ${DOCS} && cp -r . ${DOCS}/' }
}
}
}""",
}
print('[서버에서 Gitea repo Jenkinsfile push]')
for repo, content in JENKINSFILES.items():
print(f'\n=== {repo} ===')
# 임시 디렉토리에 clone
tmp = f'/tmp/jf_{repo}'
run(f'clone {repo}', f"""
rm -rf {tmp}
git clone {GITEA}/{repo}.git {tmp} 2>&1 | tail -2
""")
# Jenkinsfile 작성
with sftp.open(f'{tmp}/Jenkinsfile', 'w') as f:
f.write(content)
print(f' Jenkinsfile 작성됨')
# commit + push
run(f'push {repo}', f"""
cd {tmp}
git config user.email "ci@zioinfo.co.kr"
git config user.name "CI Bot"
git add Jenkinsfile
git diff --cached --stat
git commit -m "ci: add Jenkinsfile for CI/CD pipeline" 2>/dev/null || echo "already committed"
git push origin main 2>&1 | tail -3
rm -rf {tmp}
""", timeout=30)
# zioinfo-web, guardia-itsm 도 Jenkinsfile 확인
print('\n[기존 repo Jenkinsfile 존재 확인]')
for repo in ['zioinfo-web', 'guardia-itsm']:
run(f'{repo} Jenkinsfile', f"""
curl -sf '{GITEA.replace("http://zio:Zio%40Admin2026%21@localhost", "http://localhost")}:3000/api/v1/repos/zio/{repo}/contents/Jenkinsfile' \
-u 'zio:Zio@Admin2026!' 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print('OK size:', d.get('size',0))" 2>/dev/null || echo "없음"
""")
sftp.close()
client.close()
print('\n=== 완료 ===')

View File

@ -0,0 +1,53 @@
"""deploy_server.py git pull URL 수정 (@ → %40, ! → %21)"""
import paramiko, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('101.79.17.164', username='root', password='1q2w3e!Q', timeout=15)
def run(label, cmd, timeout=30):
print(f'\n[{label}]')
_, o, e = client.exec_command(cmd, timeout=timeout)
out = o.read().decode('utf-8', 'replace').strip()
if out: print(out[:600])
return out
# 현재 deploy_server.py에서 Gitea URL 패턴 확인
run('현재 URL 패턴', "grep -n 'localhost:3000\|gitea\|git pull\|git clone' /opt/zioinfo/deploy_server.py | head -20")
# URL 수정: localhost:3000 → 127.0.0.1:9003, @Admin → %40Admin, ! → %21
run('URL 수정', r"""
sed -i \
's|http://localhost:3000/|http://127.0.0.1:9003/|g' \
/opt/zioinfo/deploy_server.py
sed -i \
's|http://zio:Zio@Admin2026!@|http://zio:Zio%40Admin2026%21@|g' \
/opt/zioinfo/deploy_server.py
sed -i \
's|zio:Zio@Admin2026!@127.0.0.1|zio:Zio%40Admin2026%21@127.0.0.1|g' \
/opt/zioinfo/deploy_server.py
echo "수정 완료"
""")
# 수정 결과 확인
run('수정 후 URL 패턴', "grep -n 'gitea\|git pull\|git clone\|9003\|3000' /opt/zioinfo/deploy_server.py | head -20")
# 서비스 재시작
run('서비스 재시작', 'systemctl restart zioinfo-deploy && sleep 2 && systemctl is-active zioinfo-deploy')
# 테스트: guardia-itsm 배포 트리거
run('배포 트리거 테스트', """
curl -sf -X POST http://localhost:9999 \
-H 'Content-Type: application/json' \
-H 'X-Gitea-Event: push' \
-d '{"repository":{"name":"guardia-itsm"},"ref":"refs/heads/main"}' \
2>/dev/null && echo "queued"
""")
import time
time.sleep(5)
run('배포 로그 확인', 'tail -15 /var/log/zioinfo/deploy.log 2>/dev/null')
client.close()
print('\n=== URL 수정 완료 ===')

View File

@ -0,0 +1,41 @@
"""deploy_server.py git URL 정확히 수정"""
import paramiko, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('101.79.17.164', username='root', password='1q2w3e!Q', timeout=15)
def run(label, cmd, timeout=30):
print(f'\n[{label}]')
_, o, e = client.exec_command(cmd, timeout=timeout)
out = o.read().decode('utf-8', 'replace').strip()
if out: print(out[:800])
return out
# 서버에서 deploy_server.py 내용 읽어서 URL 패턴 전체 확인
run('전체 git 관련 라인', "grep -n 'git\|clone\|pull\|localhost\|9003\|3000' /opt/zioinfo/deploy_server.py")
# @localhost:3000 → @127.0.0.1:9003 으로 교체
run('URL 교체', r"""
sed -i 's/@localhost:3000\//@127.0.0.1:9003\//g' /opt/zioinfo/deploy_server.py
echo "완료"
grep -n 'localhost\|9003' /opt/zioinfo/deploy_server.py
""")
# 서비스 재시작
run('서비스 재시작', 'systemctl restart zioinfo-deploy && sleep 2 && systemctl is-active zioinfo-deploy')
# 실제 배포 트리거 테스트
run('guardia-itsm 배포 트리거', """
curl -sf -X POST http://localhost:9999 \
-H 'Content-Type: application/json' \
-H 'X-Gitea-Event: push' \
-d '{"repository":{"name":"guardia-itsm"},"ref":"refs/heads/main"}' \
2>/dev/null
""")
import time; time.sleep(8)
run('배포 결과 로그', 'tail -20 /var/log/zioinfo/deploy.log 2>/dev/null')
client.close()

View File

@ -0,0 +1,57 @@
"""서버 git clone remote URL 수정 (localhost:3000 → 127.0.0.1:9003)"""
import paramiko, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('101.79.17.164', username='root', password='1q2w3e!Q', timeout=15)
def run(label, cmd, timeout=20):
print(f'\n[{label}]')
_, o, e = client.exec_command(cmd, timeout=timeout)
out = o.read().decode('utf-8', 'replace').strip()
if out: print(out[:400])
return out
GITEA_URL = 'http://zio:Zio%40Admin2026%21@127.0.0.1:9003/zio'
REPOS = {
'/opt/zioinfo/src': f'{GITEA_URL}/zioinfo-web.git',
'/opt/guardia/src': f'{GITEA_URL}/guardia-itsm.git',
'/opt/manager/src': f'{GITEA_URL}/guardia-manager.git',
'/opt/guardia-docs/src': f'{GITEA_URL}/guardia-docs.git',
}
for path, url in REPOS.items():
run(f'remote update {path}', f"""
if [ -d {path}/.git ]; then
git -C {path} remote set-url origin '{url}'
echo "updated: $(git -C {path} remote get-url origin)"
else
echo "no clone at {path} (will clone on first deploy)"
fi
""")
# zioinfo-web git pull 테스트
run('zioinfo-web git pull 테스트', f"""
if [ -d /opt/zioinfo/src/.git ]; then
git -C /opt/zioinfo/src pull origin main 2>&1 | tail -3
else
git clone '{GITEA_URL}/zioinfo-web.git' /opt/zioinfo/src 2>&1 | tail -3
fi
""", timeout=30)
# 배포 재트리거
run('zioinfo-web 재트리거', """
curl -sf -X POST http://localhost:9999 \
-H 'Content-Type: application/json' \
-H 'X-Gitea-Event: push' \
-d '{"repository":{"name":"zioinfo-web"},"ref":"refs/heads/main"}' \
2>/dev/null
""")
import time; time.sleep(8)
run('최종 배포 로그', 'tail -15 /var/log/zioinfo/deploy.log 2>/dev/null')
client.close()
print('\n=== git remote 수정 완료 ===')

View File

@ -0,0 +1,43 @@
"""webhook 서버 포트 충돌 해결 + Jenkinsfile Gitea push"""
import paramiko, sys, subprocess, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('101.79.17.164', username='root', password='1q2w3e!Q', timeout=15)
def run(label, cmd, timeout=30):
print(f'\n[{label}]')
_, o, e = client.exec_command(cmd, timeout=timeout)
out = o.read().decode('utf-8', 'replace').strip()
print(out[:600] if out else '(empty)')
return out
# 1. 포트 9999 점유 프로세스 강제 종료
run('포트 9999 프로세스 확인', 'ss -tlnp | grep 9999')
run('포트 9999 kill', 'fuser -k 9999/tcp 2>/dev/null; sleep 1; ss -tlnp | grep 9999 || echo "포트 해제됨"')
# 2. systemd 서비스 재시작
run('서비스 재시작', 'systemctl restart zioinfo-deploy && sleep 3 && systemctl is-active zioinfo-deploy')
run('서비스 상태', 'systemctl status zioinfo-deploy --no-pager | head -10')
# 3. webhook 동작 확인
run('webhook 트리거 테스트', """
curl -sf -X POST http://localhost:9999 \
-H 'Content-Type: application/json' \
-H 'X-Gitea-Event: push' \
-d '{"repository":{"name":"guardia-itsm"}}' 2>/dev/null || echo "연결 실패"
""")
# 4. Gitea webhook 등록 상태 확인 (zioinfo-web 포함 5개)
REPOS = ["zioinfo-web", "guardia-itsm", "guardia-manager", "guardia-messenger", "guardia-docs"]
for repo in REPOS:
run(f'webhook {repo}', f"""
R=$(curl -sf 'http://127.0.0.1:9003/api/v1/repos/zio/{repo}/hooks' \
-u 'zio:Zio@Admin2026!' 2>/dev/null | \
python3 -c "import sys,json; d=json.load(sys.stdin); [print(h.get('id'), h.get('config',{{}}).get('url',''), 'active:', h.get('active')) for h in d]" 2>/dev/null)
[ -z "$R" ] && echo "webhook 없음" || echo "$R"
""")
client.close()
print('\n=== webhook 서버 수정 완료 ===')