Best Practice - Obsidian Vault와 Next.js 블로그의 자동 동기화 구현
1. 개요
Next.js를 사용하여 Obsidian vault를 블로그로 활용하는 웹서비스를 개발했다.
이 시스템에서 Obsidian vault의 변경사항을 자동으로 웹서비스에 반영하는 방법을 구현해보자.
기존 시스템 구조
- Obsidian vault는 GitHub 저장소에 호스팅됨
- 웹서비스의 로컬 환경에서 Obsidian 저장소에 접근 가능
목표
Obsidian vault가 변경될 때 자동으로 웹서비스의 콘텐츠를 업데이트
2. 해결방법
- Obsidian vault 저장소에 webhook 설정
- Push 이벤트 발생 시 웹서비스에 POST 요청 전송
- 웹서비스에 webhook 수신 엔드포인트 생성
- 수신된 webhook 유효성 검증
- 유효한 요청에 대해
git pull실행으로 vault 업데이트
3. 구현과정
3.1 GitHub Webhook 설정
참고자료 : https://docs.github.com/ko/webhooks/using-webhooks/creating-webhooks
-
GitHub 저장소 설정에서 Webhooks 추가
-
Payload URL 설정 (예:
http://yourdomain.com/api/webhooks) -
콘텐츠 타입을
application/json으로 설정 -
Push 이벤트만 선택
그러나 아무나 내 사이트에 Post 요청을 보낼 수 있고, 요청에 따라 서버 스크립트가 실행된다면 문제가 발생할 수 밖에 없음.
- 그러므로 인증이 필요함
3.2 보안 설정
Webhook의 보안을 위해 비밀키를 생성하고 설정
- github webhook 요청이 유효한지 검증하기 위해서는 비밀키를 설정하면 됨
- 비밀키는 뭐 따로 웹에서하는게 아니라 로컬 터미널에서 아래와 같이 명령어를 실행할 수 있음.
openssl rand -hex 20생성된 키를 GitHub Webhook 설정의 'Secret' 필드와 웹서비스의 환경 변수에 추가
3.3 Webhook 수신 엔드포인트 구현
Next.js API 라우트에 POST 요청 처리 로직을 추가
-
GitHub Webhooks 인증 설정
- github webhook 요청이 유효한지 검증하기 위해서는 비밀키를 설정하면 됨
- 비밀키는 뭐 따로 웹에서하는게 아니라 로컬 터미널에서 아래와 같이 명령어를 실행할 수 있음.
openssl rand -hex 20- 결과를 레파지토리 .env에 추가하고 Manage webhook 의 secret에도 추가함
- GitHub Webhooks 설정이 끝나면 route에서 post요청 시 검증 이후 git pull하는 로직을 추가할 수 있다.
- 대략적인 코드는 아래와 같다.
export async function POST(
request: NextRequest,
context: { params: Params }
): Promise<NextResponse<ApiResponse>> {
// github webhook으로 push가 발생하면 이곳에서 git pull을 실행하고 last pull time을 업데이트
try {
const body = await request.text();
// step 1: github에서 온 webhook인지 확인
if (!verifyGithubWebhook(request, body)) {
console.error("Invalid webhook signature");
return NextResponse.json(
{ error: "Invalid webhook signature" },
{ status: 403 }
);
}
// step 2: webhook이 push 이벤트인지 확인
const jsonBody = JSON.parse(body);
console.log("Received valid webhook from GitHub");
console.log("Event:", request.headers.get("x-github-event"));
console.log("Action:", jsonBody.action);
console.log("Pulling latest changes from git repository");
if (jsonBody.action !== "push") {
console.error("Webhook action is not push, skipping git pull");
return NextResponse.json(
{ content: "Webhook processed successfully" },
{ status: 200 }
);
}
// step 3: git pull 실행
try {
const { stdout, stderr } = await execAsync("git pull origin main", {
cwd: REPO_PATH,
});
console.log("Git pull output:", stdout);
if (stderr) console.error("Git pull stderr:", stderr);
} catch (error) {
console.error("Error during git pull:", error);
return NextResponse.json(
{ error: "Failed to pull latest changes" },
{ status: 500 }
);
}
// step 4: last pull time 업데이트
await fs.writeFile(LAST_PULL_TIME_FILE, Date.now().toString());
// step 5: webhook 처리 완료
return NextResponse.json(
{ content: "Webhook processed and git pull executed successfully" },
{ status: 200 }
);
} catch (error) {
console.error("Error processing webhook:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}결과적으로 git push 가 이루어지면 해당 문서가 웹서비스에 제대로 업데이트되는지 확인한다
- 왜 안되는거지?
- 파라미터를 잘못 파악함.
jsonBody.action가 아니라request.headers.get("x-github-event")를 사용해야 함.- 로직을 아래와 같이 수정함
const event = request.headers.get("x-github-event");
console.log("Event:", event);
if (event !== "push") {
console.error("Webhook action is not push, skipping git pull");
return NextResponse.json(
{ content: "Webhook processed successfully" },
{ status: 200 }
);
}다시 테스트 시 잘 되는 것을 확인함.
4. 주요 구현 포인트
- Webhook 유효성 검증:
verifyGithubWebhook함수를 사용하여 요청의 진위를 확인. - 이벤트 타입 확인: 'push' 이벤트만 처리하도록 함.
- git pull 실행: 유효한 push 이벤트에 대해
git pull을 실행. - 마지막 업데이트 시간 기록: 성공적인 업데이트 후 타임스탬프를 파일에 저장.
5. 결과 및 향후 개선 사항
이 구현으로 Obsidian vault에 변경사항이 push될 때마다 자동으로 웹서비스의 콘텐츠가 업데이트.
향후 개선 가능한 부분:
- 에러 처리 강화: 네트워크 문제나 Git 충돌 등에 대한 더 세밀한 처리
- 로깅 개선: 상세한 로그 기록으로 문제 해결 용이성 향상
- 성능 최적화: 대규모 변경사항에 대한 효율적인 처리 방법 고려
이 시스템을 통해 Obsidian vault와 웹서비스 간의 동기화가 자동화되어, 콘텐츠 관리가 훨씬 더 효율적으로 이루어질 수 있게 되었음.
댓글
첫 번째 댓글을 남겨보세요.