블로그 운영에서는 글을 발행한 뒤 링크를 따로 정리해서 SNS에 올리는 작업이 자주 남습니다. 이번에는 그 반복 작업을 줄이기 위해 GitHub Actions로 새 글 추가를 감지하고, upload-post API를 호출하는 흐름을 붙였습니다.
전체 흐름
현재 워크플로는 아래 순서로 동작합니다.
main브랜치에 push가 발생함- 변경 경로가
content/blog/**/*.md와 맞는지 확인함 - 이번 push에서 새로 추가된 markdown 파일만 골라냄
- Python 스크립트가 각 파일의
title,summary를 읽음 - SNS에 올릴 문구를 만들고
upload-post로 전송함
즉 수정된 글 전체를 다시 알리는 방식이 아니라, git diff --diff-filter=A를 사용해 이번에 추가된 새 파일만 공지합니다. 폴더 소개용 _index.md는 스크립트에서 제외합니다.
.github에 무엇을 두었는가
이 프로젝트에서 실제로 사용하는 위치는 아래 두 군데입니다.
.github/
workflows/
notify-new-posts.yml
scripts/
notify_new_posts.py
각 역할은 명확하게 나눴습니다.
workflows/notify-new-posts.yml: 언제 실행할지, 어떤 런타임을 쓸지, 어떤 환경 변수를 넘길지 담당scripts/notify_new_posts.py: 파일 목록을 읽고 front matter를 파싱해서 API를 호출하는 실제 로직 담당
이렇게 나누면 워크플로 파일은 배선만 맡고, 로직 변경은 Python 스크립트에서 처리할 수 있어서 유지보수가 편합니다.
워크플로 파일에서 하는 일
현재 YAML의 핵심은 아래와 같습니다.
name: Notify new blog posts
on:
push:
branches:
- main
paths:
- 'content/blog/**/*.md'
jobs:
notify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine added blog files
id: diff
run: |
ADDED=$(git diff --name-only --diff-filter=A "$BEFORE" "$AFTER" -- 'content/blog/**/*.md' || true)
- uses: actions/setup-python@v5
if: steps.diff.outputs.added != ''
with:
python-version: '3.12'
- name: Install dependencies
if: steps.diff.outputs.added != ''
run: pip install upload-post pyyaml
- name: Notify upload-post.com
if: steps.diff.outputs.added != ''
env:
UPLOAD_POST_API_KEY: ${{ secrets.UPLOAD_POST_API_KEY }}
UPLOAD_POST_USER: ${{ secrets.UPLOAD_POST_USER }}
UPLOAD_POST_PLATFORMS: ${{ vars.UPLOAD_POST_PLATFORMS }}
ADDED_FILES: ${{ steps.diff.outputs.added }}
run: python .github/scripts/notify_new_posts.py
여기서 중요하게 본 부분은 세 가지였습니다.
paths로 블로그 markdown 변경에만 반응하게 제한한 점- 새 파일이 없으면 Python 설치와 API 호출을 건너뛰게 한 점
- 민감 정보는
Secrets, 운영 옵션은Vars로 나눈 점
Secrets는 무엇을 넣었는가
GitHub Actions에서 Secrets는 로그에 노출되면 안 되는 값을 보관할 때 씁니다. 이 프로젝트에서는 아래 두 개를 넣었습니다.
UPLOAD_POST_API_KEY
UPLOAD_POST_USER
실제 사용 기준으로 보면 다음처럼 나뉩니다.
UPLOAD_POST_API_KEY: 반드시 비공개여야 하는 인증 키UPLOAD_POST_USER: API 요청에 함께 보내는 사용자 식별값
UPLOAD_POST_USER는 성격상 공개 가능 여부를 팀 정책에 따라 다르게 볼 수 있지만, 지금 구성에서는 함께 Secrets로 넣어 관리했습니다. 운영 기준이 아직 작고 단순하다면 이 편이 덜 헷갈립니다.
Secrets 설정 방법
GitHub 저장소에서 아래 순서로 추가할 수 있습니다.
- 저장소 페이지로 이동
SettingsSecrets and variablesActionsNew repository secret
이름은 워크플로에서 쓰는 값과 정확히 같아야 합니다.
UPLOAD_POST_API_KEY
UPLOAD_POST_USER
Vars는 무엇을 넣었는가
Vars는 민감하지 않지만 배포 환경에서 바뀔 수 있는 값을 둘 때 편합니다. 이 프로젝트에서는 플랫폼 목록을 Vars로 분리했습니다.
UPLOAD_POST_PLATFORMS
예를 들면 아래처럼 설정할 수 있습니다.
x
x,threads
x,threads,bluesky
Python 스크립트에서는 이 문자열을 쉼표로 나누어 리스트로 바꿉니다. 즉 플랫폼을 늘리거나 줄일 때 YAML이나 Python 코드를 수정하지 않고도 저장소 설정에서 조정할 수 있습니다.
Vars 설정 방법
경로는 Secrets와 거의 같습니다.
- 저장소 페이지로 이동
SettingsSecrets and variablesActionsVariables탭 선택New repository variable
이름은 UPLOAD_POST_PLATFORMS, 값은 예를 들어 x,threads처럼 넣으면 됩니다.
Python 스크립트에서 하는 일
워크플로가 넘긴 ADDED_FILES를 받아서 실제로 처리하는 쪽은 .github/scripts/notify_new_posts.py입니다. 이 스크립트는 다음 기준으로 동작합니다.
content/blog/아래 파일만 처리함_index.md는 제외함- 파일이 실제로 존재하는지 확인함
- front matter에
title,summary가 둘 다 있을 때만 업로드함
이 기준을 둔 이유는 폴더 소개 페이지나 초안 상태 문서를 잘못 알리는 일을 줄이기 위해서입니다. 특히 summary를 필수로 본 덕분에 SNS에 올라가는 문구 품질을 front matter 단계에서 같이 관리할 수 있었습니다.
운영하면서 느낀 점
GitHub Actions로 자동화할 때 중요한 것은 복잡한 기능을 한 번에 다 넣는 것보다, 실패했을 때 어디를 보면 되는지 흐름을 분리해 두는 것입니다. 이 구성에서는 다음이 특히 도움이 됐습니다.
- 트리거는 YAML에서 관리
- 메시지 생성은 Python에서 관리
- 인증 정보는
Secrets - 플랫폼 선택은
Vars
이렇게 나누면 어느 단계에서 바꿔야 할지 바로 보입니다. 예를 들어 플랫폼을 늘리는 것은 Vars 수정으로 끝나고, 문구 형식을 바꾸는 것은 Python 스크립트만 손보면 됩니다.
정리
새 글 알림 자동화는 기능 자체보다 경계 정리가 더 중요했습니다. GitHub Actions는 실행 조건과 환경 전달만 맡기고, 실제 공지 생성은 Python으로 분리해 두니 수정 포인트가 분명해졌습니다. 블로그를 계속 운영할수록 이런 작은 반복 작업을 저장소 안에서 닫아 두는 방식이 훨씬 편합니다.