Skip to content

ginishuh/traum_homepage

Repository files navigation

TRR Website & Blog

정적 홈페이지(www.trr.co.kr)와 블로그(blog.trr.co.kr)를 최소한의 인프라로 운영하는 단일 레포입니다. 프로덕션에서는 홈페이지/블로그를 호스트 Nginx가 정적으로 서빙하고, OAuth만 컨테이너로 유지합니다.

폴더 구조

  • 운영 가이드: docs/OPERATIONS.md
  • src/ — 홈페이지 정적 파일(HTML/CSS/JS)
  • Dockerfile — (로컬 개발용) nginx unprivileged, 8080
  • nginx.conf — (로컬 개발용) 보안 헤더/캐시/압축 기본 설정
  • docker-compose.yml — (로컬 개발용) web 서비스
  • traum_blog/ — Hugo + Decap CMS 블로그(별도 compose/Dockerfile 포함)
    • static/brand/ — 회사 로고 등 브랜드 에셋(홈/블로그 공용)

환경변수(.env)

홈페이지 컨테이너 바인딩은 .env로 조정합니다. 예시는 .env.example 참고.

HTTP_BIND_HOST=127.0.0.1
HOMEPAGE_PORT=17201

로컬 실행

권장: Admin(/admin)까지 포함한 E2E 개발은 docker compose로 블로그+OAuth를 실행하세요. 웹은 단순 정적 미리보기면 비컨테이너도 가능하지만, Nginx 헤더/캐시 동작까지 검증하려면 web 서비스로 실행합니다.

# 홈페이지 (http://127.0.0.1:17201)
docker compose build web && docker compose up -d web

# 블로그 (http://127.0.0.1:17202)
cd traum_blog
cp static/admin/config.dev.yml static/admin/config.yml
# (필요 시) .env에 OAUTH_TEST_MODE=1 설정 후
docker compose build blog && docker compose up -d blog oauth

# CMS Admin (Decap)
# - 로컬: http://localhost:17202/admin/
# - 운영: https://blog.trr.co.kr/admin/
# - 필드: Title, Date, Draft, Category(select: market|ops|behind|story), Summary, KPIs(list of {label,value}), Description, Tags, Body

로컬 개발 모드 요약

  • 기본 권장: docker compose up -d blog oauth (Admin/OAuth 포함 전체 플로우 검증)
  • 빠른 미리보기(웹만): cd src && python3 -m http.server 17201 --bind 127.0.0.1 (또는 npx serve)
  • Nginx 동작까지 검증: docker compose up -d web

VPS 배포(프로덕션)

  1. DNS
    • www.trr.co.kr → VPS IP
    • blog.trr.co.kr → VPS IP
  2. 정적 루트(Nginx 예시)
server {
    listen 80; server_name www.trr.co.kr;
    root /srv/www/trr;
    index index.html;
}
server {
    listen 80; server_name blog.trr.co.kr;
    root /srv/traum_homepage/traum_blog/public;
    index index.html;
    location /oauth/ { proxy_pass http://127.0.0.1:17203/; }
}
  1. TLS는 certbot 또는 Caddy/Traefik 권장.

블로그(Decap CMS)

  • 관리페이지
    • 운영: https://blog.trr.co.kr/admin/
    • 로컬: http://localhost:17202/admin/
  • GitHub OAuth App 등록(Homepage/Callback URL은 README의 블로그 섹션 참조)
  • 설정 파일 복사
    • 로컬 개발: cp traum_blog/static/admin/config.dev.yml traum_blog/static/admin/config.yml
    • 운영 배포: 워크플로가 자동으로 static/admin/config.prod.ymlconfig.yml로 복사합니다 (없을 경우)
  • traum_blog/.env.example을 복사해 값 설정 후 oauth 서비스 기동
    • 운영/실제 로그인: DEV_ALLOW_ALL_ORIGINS=0, OAUTH_TEST_MODE=0
    • 로컬 테스트 토큰(팝업 없이 즉시 발급)이 필요할 때만 OAUTH_TEST_MODE=1로 전환하고 테스트 후 다시 0으로 되돌리세요.
    • .env 수정 후에는 docker compose up -d --force-recreate --no-deps oauth 로 OAuth 컨테이너를 재시작해야 적용됩니다.
  • E2E 확인: OAUTH_TEST_MODE=1 npx playwright test (사전에 cd tests/e2e && npm install)

OAuth 서버 (TypeScript)

  • 소스 경로: traum_blog/oauth/src/ (TypeScript, ESM)
  • 로컬 실행 예시
    cd traum_blog/oauth
    npm install
    npm run build
    node dist/server.js
    # 또는 Compose로: cd traum_blog && docker compose up -d --build oauth
  • 환경 변수(요약, 자세한 항목은 traum_blog/.env.example 참고)
    • 필수: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
    • URL/오리진: OAUTH_REDIRECT_URL, ALLOWED_ORIGINS
    • 스코프: GITHUB_SCOPE = public_repo(공개) 또는 repo(비공개)
    • 동작: OAUTH_TEST_MODE, DEV_ALLOW_ALL_ORIGINS, OAUTH_AUTOCLOSE, OAUTH_AUTOCLOSE_DELAY_MS, OAUTH_SUCCESS_BURST_*
    • 메트릭(옵션): METRICS_ENABLED, METRICS_BASIC_AUTH_USER, METRICS_BASIC_AUTH_PASS
  • 보안
    • 팝업 응답은 CSP nonce 기반 스크립트를 사용(unsafe-inline 제거)
    • 운영에서 /oauth/metrics는 차단 또는 Basic Auth 보호 권장(Nginx 예시 하단 참고)

Admin 오류 트러블슈팅

  • 증상: Admin 접속 시 "Failed to load config.yml (404)" 또는 콘솔에 "Response was not yaml" 출력
  • 원인: static/admin/config.yml 누락 또는 MIME이 YAML이 아님
  • 조치
    1. 프로덕션: Actions가 자동 생성하지만, 필요 시 수동 생성
      • cp traum_blog/static/admin/config.prod.yml traum_blog/static/admin/config.yml
      • Hugo 빌드 후 산출물(public/admin/config.yml)이 있는지 확인
    2. Nginx에서 정확한 MIME 고정(운영 vhost에 추가)
      # blog.trr.co.kr 서버 블록 내부
      location = /admin/config.yml {
          types { };
          default_type text/yaml;
          try_files $uri =404;
      }
    3. 검증: curl -I https://blog.trr.co.kr/admin/config.yml200 이고 Content-Type: text/yaml

브랜드 에셋 경로(공용)

  • 저장 위치: traum_blog/static/brand/
  • 프로덕션 참조 URL: https://blog.trr.co.kr/brand/<파일명>.svg
  • 로컬 참조 URL: http://127.0.0.1:17202/brand/<파일명>.svg
  • 캐시 주의: SVG는 30일 + immutable → 교체 시 파일명 버저닝(예: logo.20251028.svg)

자동배포(블로그) · GitHub Secrets

블로그는 커밋 시 GitHub Actions가 Hugo로 빌드하고 결과물(traum_blog/public/)만 VPS로 rsync 동기화합니다.

  • 워크플로: .github/workflows/deploy-blog.yml
  • Secrets(레포 Settings → Secrets and variables → Actions)
    • Name DEPLOY_HOST → Value 서버_IP
    • Name DEPLOY_USER → Value root (또는 deploy)
    • Name DEPLOY_SSH_PORT → Value 22 (기본값이면 생략 가능)
    • Name DEPLOY_SSH_KEY → Value (SSH 개인키 전체. 예: -----BEGIN OPENSSH PRIVATE KEY----- ...)
      • 권장: 배포 전용 키 생성 후 공개키는 VPS ~/.ssh/authorized_keys에 등록
      • 예시(로컬/Git Bash):
        • ssh-keygen -t ed25519 -C "gh-actions-deploy-trr" -f ~/.ssh/gh_actions_trr -N ''
        • ssh-copy-id -i ~/.ssh/gh_actions_trr.pub root@서버_IP
        • ssh -i ~/.ssh/gh_actions_trr root@서버_IP 'echo OK' (접속 확인)

배포 흐름: CMS에서 글 발행(=커밋) → Actions 자동 실행 → 수십 초 내 반영(컨테이너 재시작 없음).

주의

  • .env와 인증서는 커밋 금지(.gitignore 반영)
  • 컨테이너는 비루트 이미지(nginxinc/nginx-unprivileged) 사용, 포트는 8080 고정

자동배포(웹/CD)

홈페이지(web)는 main 푸시 시 정적 파일을 VPS로 동기화합니다(컨테이너 미사용).

  • 워크플로: .github/workflows/deploy-web.yml
  • 트리거: src/** 변경 또는 수동 실행
  • 동작: SSH/rsync로 src//srv/www/trr/ 동기화 (필요 시 nginx reload)

프로덕션 준비 체크리스트(웹 정적 배포)

  • Nginx vhost 설정 확인
    • www.trr.co.krroot /srv/www/trr; (정적 서빙)
    • blog.trr.co.krroot /srv/traum_homepage/traum_blog/public;
    • location /oauth/ { proxy_pass http://127.0.0.1:17203/; }
  • 디렉터리 존재/권한
    • mkdir -p /srv/www/trr /srv/traum_homepage/traum_blog/public
    • 웹 서버 사용자(예: www-data)가 읽을 수 있도록 퍼미션 확인
  • (선택) nginx reload 권한 위임
    • 워크플로는 sudo -n systemctl reload nginx || sudo -n nginx -s reload || true로 재시도를 시도합니다.
    • 비밀번호 없이 재로드를 허용하려면 호스트에서 sudoers 항목을 제한적으로 추가하세요(사용자/경로는 환경에 맞게 조정).
      # /etc/sudoers.d/trr-nginx-reload
      deploy ALL=(root) NOPASSWD:/bin/systemctl reload nginx,/usr/sbin/nginx -s reload
      
      • deploy를 배포 계정으로 치환
      • 보안상 필요한 커맨드만 허용
  • 검증(예)
    • curl -sI https://www.trr.co.kr | sed -n '1,10p'
    • curl -sI https://blog.trr.co.kr/admin/config.yml | rg -i '200|Content-Type: text/yaml'
    • 정적 자산 응답 헤더에 Cache-Control: public, max-age=2592000, immutable 확인
    • 스타일/스크립트 변경 시는 쿼리스트링 버전(?v=YYYYMMDD)을 갱신
  • 롤백 계획(요약)
    • vhost를 이전 프록시 구성으로 되돌림: www/blog127.0.0.1:17201/17202로 프록시
    • 필요 시 docker compose build web && docker compose up -d web

Nginx: /oauth/metrics 보호(권장)

# 완전 차단
location = /oauth/metrics { return 403; }

# 또는 Basic Auth 적용 (예시)
# location = /oauth/metrics {
#   auth_basic "Restricted";
#   auth_basic_user_file /etc/nginx/.htpasswd_metrics;
#   proxy_pass http://127.0.0.1:17203/metrics;
# }

로컬 개발 팁

  • 컨테이너 포트 바인딩은 .env로 조정 가능합니다(HTTP_BIND_HOST, HOMEPAGE_PORT).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages