[개발][HomeLab] n8n과 Python으로 음력 생일 Google Calendar에 평생 자동 박제하기 (시행착오 포함)

매년 돌아오는 부모님의 음력 생일, 매번 달력을 찾아보고 캘린더에 수동으로 등록하는 것이 번거로웠다. 이번 기회에 n8nPython(FastAPI) 을 활용해, 매년 1월 1일에 그 해의 음력 생일을 자동으로 계산해서 Google Calendar에 등록하는 시스템을 구축했다.

이 글은 그 과정에서의 아키텍처 설계와, 꽤나 고생했던 시행착오들을 정리한 기록이다.


1. 아키텍처 구성

  • 서버: Intel N100 미니 PC (Ubuntu Server + Docker)
  • API 서버: Python (FastAPI + korean_lunar_calendar 라이브러리)
  • 자동화 도구: n8n (Docker)
  • 목표:
    1. n8n이 매년 1월 1일에 트리거 발생
    2. Python 서버에 "올해 이 음력 날짜가 양력으로 언제야?" 요청
    3. 반환받은 날짜를 Google Calendar에 등록 (중복 방지 처리 포함)

2. Python 음력 변환 API 서버 구축 (Docker)

n8n 자체적으로는 한국의 복잡한 음력(윤달, 평달, 작은 달 등)을 계산하기 어렵다. 따라서 Python의 korean_lunar_calendar 라이브러리를 사용하는 마이크로서비스를 N100 서버에 띄우기로 했다.

1) Dockerfile & docker-compose

안정성을 위해 Python 3.11-slim 이미지를 사용했다.

시행착오 1: 호스트 바인딩 (0.0.0.0) 처음에 uvicorn main:app으로만 실행했더니, n8n(외부 컨테이너)에서 접속이 불가능했다. 컨테이너 내부의 localhost는 외부와 격리되어 있기 때문이다. --host 0.0.0.0 옵션을 추가하여 외부 접속을 허용해야 한다.

YAML

# docker-compose.yml
version: '3.8'
services:
  lunar-api:
    build: .
    container_name: lunar-api
    ports:
      - "8000:8000"
    restart: unless-stopped

2) 핵심 로직 (main.py)과 '작은 달' 문제 해결

가장 큰 난관은 "음력 30일이 없는 달(작은 달)" 처리였다. 음력 9월 30일이 생일인 경우, 어떤 해에는 9월이 29일까지밖에 없어 에러가 발생했다.

해결책:

  1. 윤달 계산이 실패하면 평달로 자동 재시도.
  2. 30일이 없는 달(29일까지만 있는 달)인 경우, 자동으로 29일로 보정해서 계산하도록 로직 추가.

Python

# main.py (최종 수정본)
@app.post("/convert")
def convert_lunar_to_solar(date: LunarDateRequest):
    try:
        # 1. 기본 변환 시도
        is_valid = calendar.setLunarDate(date.year, date.month, date.day, date.intercalation)
        
        # 2. [예외처리] 30일이 없는 작은 달의 경우 -> 29일로 변경해서 재시도
        if not is_valid and date.day == 30:
            is_valid = calendar.setLunarDate(date.year, date.month, 29, date.intercalation)

        # 3. [예외처리] 윤달 요청했으나 윤달이 없는 해 -> 평달로 변경
        if not is_valid and date.intercalation:
            is_valid = calendar.setLunarDate(date.year, date.month, date.day, False)
            # 평달로 바꿨는데 또 작은 달인 경우
            if not is_valid and date.day == 30:
                is_valid = calendar.setLunarDate(date.year, date.month, 29, False)

        if not is_valid:
             raise ValueError("변환 실패")

        # ... (이하 리턴 로직)

3. n8n 워크플로우 구성 및 에러 해결

1) Schedule Trigger (Cron)

매년 1월 1일 자정에 실행되도록 Cron Expression을 설정했다.

  • Expression: 0 0 0 1 1 * (초 분 시 일 월 요일)

2) Google Calendar ID 포맷 에러 (Critical)

중복 등록을 막기 위해 Google Calendar 노드의 ID 옵션을 사용했다. 처음에는 가독성을 위해 언더바(_)를 사용했다.

  • 시도한 ID: lunar_{{year}}_{{month}}_{{day}}
  • 결과: 400 Bad Request (Invalid resource id value)

원인 및 해결: Google Calendar API 정책상, ID에는 언더바(_)를 사용할 수 없다. (숫자 0-9와 소문자 a-v만 허용). 따라서 언더바를 제거하고 다 붙여 쓰는 방식으로 수정하여 해결했다.

  • 수정한 ID: lunar{{year}}{{month}}{{day}} (예: `lunar2026101)

4. 최종 결과

이제 시스템은 다음과 같이 동작한다.

  1. 등록된 가족들의 음력 생일 리스트를 Python 서버로 보낸다.
  2. Python 서버는 윤달 여부와 작은 달(29일/30일)을 판단해 정확한 양력 날짜를 돌려준다.
  3. n8n은 Google Calendar에 일정을 등록한다. (이미 등록된 경우 ID 덕분에 중복 생성되지 않음)

매년 1월 1일 00:00에 n8n이 자동으로 음력 생일인 일정을 내 캘린더에 등록한다.

이제 평생 음력 생일을 챙기기 위해 달력을 뒤적거릴 필요가 없어졌다.

끝.