Obsidian-sync-blog
깃허브와 개인서버로 옵시디언의 문서를 사진과 마크다운 문서로 분리하고, 옵시디언의 마크다운 문법을 공식 마크다운 문법으로 수정하여 사용자가 정해진 경로로 업로드 해주는 옵시디언 확장프로그램
핵심 기술 스택
- 언어: TypeScript
- 빌드: esbuild
- GitHub API: @octokit/rest
- 파일 시스템: Node.js fs 모듈
- HTTP 요청: Obsidian requestUrl API
- UI: Obsidian Plugin API
주요 기능
- 발행 (Publish)
- 사용자가 노트 선택
- IntegratedPublisher가 GitHub/로컬 서버로 발행
- 마크다운 링크 변환 (
→ ![image.png].(/img/user/image.png)) - 이미지 파일 자동 처리
- Webhook으로 Docker 재시작
- 상태 관리
- 각 노트의 해시값 저장
- 4가지 상태 추적: Unpublished, Changed, Deleted, Published
- 폴더 구조 기반 UI
- 배치 처리
- Git Tree API를 사용한 원자적 커밋
- 여러 파일을 하나의 커밋으로 통합
- 중복 이미지 자동 제거
개발 방법
터미널 1
npm run dev
위의 명령을 통해, src에 개발 중인 플러그인의 수정 사항이 생기면, 이것을 확인하고 문제가 없다면 main.js 파일로 변환 해줌
터미널 2
.\deply-to-vault.ps1
위의 명령은 다음과 같은 내용을 포함함
$SOURCE_DIR = "C:\Users\gnbup\Desktop\wootech\Obsidian-sync-blog"
$VAULT_PLUGIN_DIR = "C:\Users\gnbup\OneDrive\Obsidian\.obsidian\plugins\obsidian-sync-blog"
Write-Host "=== Deploying Plugin to Obsidian ===" -ForegroundColor Cyan
Write-Host ""
$files = @("main.js", "manifest.json", "styles.css")
foreach ($file in $files) {
$sourcePath = Join-Path $SOURCE_DIR $file
$destPath = Join-Path $VAULT_PLUGIN_DIR $file
if (Test-Path $sourcePath) {
Copy-Item $sourcePath -Destination $destPath -Force
Write-Host "[OK] $file copied" -ForegroundColor Green
} else {
Write-Host "[SKIP] $file not found" -ForegroundColor Yellow
}
}
Write-Host ""
Write-Host "=== Deployment Complete ===" -ForegroundColor Green
Write-Host "Please reload Obsidian to see changes." -ForegroundColor CyanSOURCE_DIR에는 현재 이 폴더의 경로가 지정되어 있다.
VAULT_PLUGIN_DIR에는 사용중인 옵시디언 vault의 플러그인 폴더의 경로로 지정되어 있다.
그 후 main.js, manifest.json, styles.css을 플러그인 폴더로 복사함으로써 플러그인을 사용할 수 있다.
폴더 구조
Obsidian-sync-blog/
├── src/
│ ├── publisher/ # 발행 로직
│ │ ├── GitHubPublisher.ts
│ │ ├── IntegratedPublisher.ts
│ │ ├── LocalServerPublisher.ts
│ │ ├── SSHExecutor.ts
│ │ └── WebhookClient.ts
│ ├── types/ # 타입 정의
│ │ └── settings.ts
│ └── ui/ # UI 컴포넌트
│ ├── ConnectionTestModal.ts
│ ├── Notification.ts
│ ├── PublicationCenterModal.ts
│ ├── SettingTab.ts
│ └── StatusBar.ts
├── main.ts # 플러그인 진입점
├── manifest.json # 플러그인 메타데이터
├── package.json # npm 설정
├── esbuild.config.mjs # 빌드 설정
├── deploy-to-vault.ps1 # 배포 스크립트
└── styles.css # 스타일시트파일별 역할
Core Files (루트)
| 파일 | 역할 |
|---|---|
| main.ts | 플러그인의 진입점. 플러그인 초기화, 커맨드 등록, UI 초기화 담당 |
| manifest.json | 플러그인 메타데이터 (ID, 이름, 버전, 설명) |
| package.json | npm 의존성 및 스크립트 정의 |
| esbuild.config.mjs | TypeScript → JavaScript 빌드 설정 |
| deploy-to-vault.ps1 | 개발 중인 플러그인을 Obsidian vault로 배포하는 스크립트 |
| styles.css | 플러그인 UI 스타일링 |
Publisher Layer (src/publisher/)
| 파일 | 역할 |
|---|---|
| IntegratedPublisher.ts | 통합 발행 관리자. GitHub, 로컬 서버, Webhook을 조율하는 중앙 컨트롤러 |
| GitHubPublisher.ts | GitHub API를 통한 발행. Octokit 사용, Git Tree API로 원자적 커밋 생성 |
| LocalServerPublisher.ts | OMV 서버로 파일 복사. SMB 경로를 통해 직접 파일시스템 접근 |
| WebhookClient.ts | 로컬 서버에 Webhook 호출하여 Docker 컨테이너 재시작 트리거 |
| SSHExecutor.ts | (미사용) SSH를 통한 원격 명령 실행 |
Types Layer (src/types/)
| 파일 | 역할 |
|---|---|
| settings.ts | 플러그인 설정 타입 정의, 기본 설정값, 발행 상태 관리 |
UI Layer (src/ui/)
| 파일 | 역할 |
|---|---|
| PublicationCenterModal.ts | Publication Center UI. 폴더 구조, 배치 발행, 상태 필터링 |
| SettingTab.ts | 플러그인 설정 탭 (GitHub, 로컬 서버, Webhook 설정) |
| StatusBar.ts | Obsidian 하단 상태바에 발행 진행 상황 표시 |
| Notification.ts | 알림 관리 (info, success, warning, error) |
| ConnectionTestModal.ts | 연결 테스트 모달 |
다이어그램
Publication Center 플로우차트
flowchart TD Start([사용자가 Publication Center 열기]) --> ValidateSettings{설정 유효성 검사} ValidateSettings -->|유효하지 않음| ShowError[설정 오류 화면 표시] ShowError --> End1([종료]) ValidateSettings -->|유효함| AnalyzeNotes[노트 상태 분석] AnalyzeNotes --> BuildTree[폴더 트리 구조 생성] BuildTree --> RenderUI[UI 렌더링] RenderUI --> DisplaySections[4개 섹션 표시] DisplaySections --> Unpublished[📝 Unpublished Notes] DisplaySections --> Changed[✏️ Changed Notes] DisplaySections --> Deleted[🗑️ Deleted Notes] DisplaySections --> Published[✅ Published Notes] Unpublished --> UserSelection{사용자 선택} Changed --> UserSelection Deleted --> UserSelection Published --> UserSelection UserSelection -->|Publish 버튼| CheckSelection{선택된 노트 있음?} CheckSelection -->|없음| ShowWarning[경고 메시지] ShowWarning --> UserSelection CheckSelection -->|있음| DeterminePublishType{파일 개수} DeterminePublishType -->|1개| SinglePublish[단일 파일 발행] DeterminePublishType -->|여러 개| BatchPublish[배치 발행] SinglePublish --> PublishToGitHub{GitHub 발행?} BatchPublish --> PublishToGitHub PublishToGitHub -->|Yes| GitHubAPI[GitHub API 호출] PublishToGitHub -->|No| PublishToLocal GitHubAPI --> PublishToLocal{로컬 서버 발행?} PublishToLocal -->|Yes| CopyFiles[SMB를 통해 파일 복사] PublishToLocal -->|No| TriggerWebhook CopyFiles --> TriggerWebhook{Webhook 활성화?} TriggerWebhook -->|Yes| CallWebhook[Docker 재시작 Webhook 호출] TriggerWebhook -->|No| SaveMetadata CallWebhook --> SaveMetadata[발행 메타데이터 저장] SaveMetadata --> ShowSuccess[성공 알림 표시] ShowSuccess --> End2([종료]) UserSelection -->|Unpublish 버튼| ConfirmUnpublish{삭제 확인} ConfirmUnpublish -->|취소| UserSelection ConfirmUnpublish -->|확인| DeleteFromGitHub[GitHub에서 삭제] DeleteFromGitHub --> DeleteFromLocal[로컬 서버에서 삭제] DeleteFromLocal --> RemoveMetadata[메타데이터 제거] RemoveMetadata --> RefreshUI[UI 새로고침] RefreshUI --> End3([종료])
발행 프로세스 순서도
sequenceDiagram participant User participant Plugin participant IntegratedPublisher participant GitHub participant LocalServer participant Webhook User->>Plugin: 1. "Publish" 버튼 클릭 Plugin->>Plugin: 2. 현재 파일 가져오기 Plugin->>IntegratedPublisher: 3. publishFile(file) alt GitHub 발행 활성화 IntegratedPublisher->>GitHub: 4a. publishFile() GitHub->>GitHub: 4b. 마크다운 변환 GitHub->>GitHub: 4c. 이미지 처리 GitHub->>GitHub: 4d. Git Tree 생성 GitHub->>GitHub: 4e. 커밋 생성 GitHub-->>IntegratedPublisher: 4f. 성공/실패 end alt 로컬 서버 활성화 IntegratedPublisher->>LocalServer: 5a. publishFiles() LocalServer->>LocalServer: 5b. 경로 검증 LocalServer->>LocalServer: 5c. 파일 복사 (SMB) LocalServer-->>IntegratedPublisher: 5d. 성공/실패 end alt Webhook 활성화 & 로컬 발행 성공 IntegratedPublisher->>Webhook: 6a. triggerDockerRestart() Webhook->>Webhook: 6b. HTTP POST 요청 Webhook-->>IntegratedPublisher: 6c. 성공/실패 end IntegratedPublisher-->>Plugin: 7. PublishResult 반환 Plugin->>Plugin: 8. 발행 정보 저장 Plugin->>User: 9. 알림 표시
아키텍처 개요 다이어그램
graph TB subgraph "Obsidian Plugin" A[main.ts<br/>플러그인 진입점] --> B[IntegratedPublisher<br/>통합 발행 관리자] A --> C[PublicationCenterModal<br/>UI] A --> D[StatusBar<br/>상태 표시] B --> E[GitHubPublisher] B --> F[LocalServerPublisher] B --> G[WebhookClient] end subgraph "GitHub" E --> H[Octokit API] H --> I[Repository<br/>bloodmoon3929.github.io/blog] I --> J[GitHub Pages<br/>정적 사이트] end subgraph "Local Server OMV" F --> K[SMB 파일 공유<br/>\\GNBUPI\500gssd1\quartz-blog] K --> L[Quartz 소스 파일] G --> M[Webhook Endpoint<br/>:8099/restart-docker] M --> N[Docker Compose] N --> O[Quartz Container] O --> P[Nginx Load Balancer] end subgraph "User Access" J --> Q[Cloudflare Workers<br/>로드 밸런싱] P --> Q Q --> R[bloodmoon3929.duckdns.org<br/>사용자 접근] end style A fill:#e1f5ff style B fill:#fff4e1 style E fill:#ffe1e1 style F fill:#e1ffe1 style G fill:#f4e1ff