Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 41 additions & 53 deletions .github/workflows/build-with-vault-refresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ name: Build With Vault Refresh

on:
workflow_dispatch:
inputs:
vault_changed_files:
description: "바뀐 vault 경로(쉼표 구분). Watch OneDrive 가 채운다. 비우면 강제 리프레시"
type: string
default: ""
push:
branches:
- dev

# 생성물을 같은 OneDrive 경로에 쓰므로 두 실행이 겹치지 않게 한다.
concurrency:
group: build-with-vault-refresh-${{ github.ref }}
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -24,17 +34,25 @@ jobs:
- name: Install dependencies
run: npm ci

# vault 콘텐츠의 정본은 개인 OneDrive 의 hsol-info-blob 폴더다(2026-09-27 hsol-info-blob 저장소 폐기).
# sync-onedrive-to-blob.yml 이 올린 Blob info/vault 를 hsol-info-blob/vault 로 내려받아 기존 스크립트 경로를 그대로 쓴다.
# 내려받은 직후의 생성물 해시를 적어 두고, 아래 업로드 스텝이 실제로 바뀐 것만 Blob 에 되올린다.
- name: Pull vault from Blob
- name: Install rclone
run: |
curl -fsSL https://rclone.org/install.sh | sudo bash
rclone version | head -1

# 콘텐츠의 정본이자 유일한 저장소는 개인 OneDrive 의 hsol-info-blob 폴더다(2026-09-27 부터).
# vault 를 hsol-info-blob/vault 로 받아 기존 스크립트 경로를 그대로 쓴다. 사이트 생성기가 읽지 않는 경로는 받지 않는다.
# 받은 직후의 생성물 해시를 적어 두고, 뒤의 게시 스텝이 실제로 바뀐 것만 OneDrive 에 되쓴다.
- name: Pull vault from OneDrive
env:
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
# 사이트 생성기가 읽지 않는 경로는 받지 않는다(예전 Blob 에 남은 datasources 등).
BLOB_PULL_EXCLUDE: "datasources/,.obsidian/,Templates/,Attachments/"
DATABASE_URL: ${{ secrets.DATABASE_URL }}
ONEDRIVE_TOKEN_KEY: ${{ secrets.ONEDRIVE_TOKEN_KEY }}
ONEDRIVE_DRIVE_ID: ${{ secrets.ONEDRIVE_DRIVE_ID }}
run: |
set -euo pipefail
node ops/scripts/pull-blob-to-vault.mjs --vault-dir hsol-info-blob/vault --clean
node ops/scripts/with-onedrive.mjs -- rclone copy od:hsol-info-blob/vault hsol-info-blob/vault \
--exclude "datasources/**" --exclude ".obsidian/**" --exclude "Templates/**" --exclude "Attachments/**" \
--exclude "*.base" --exclude ".DS_Store" --transfers 16 --checkers 16 --stats-one-line --stats 30s
echo "받은 파일: $(find hsol-info-blob/vault -type f | wc -l)개"
mkdir -p generated
for f in vault/object-views/site-data.json vault/object-views/onepager-ko.html vault/object-views/onepager-en.html; do
if [ -f "hsol-info-blob/$f" ]; then echo "$(sha1sum "hsol-info-blob/$f" | cut -d' ' -f1) $f"; fi
Expand All @@ -49,8 +67,10 @@ jobs:
ANTHROPIC_MAX_TOKENS: "64000"
GIT_DIFF_BASE_SHA: ${{ github.event.before }}
GIT_DIFF_HEAD_SHA: ${{ github.sha }}
# vault(서브모듈) 수정이 있을 때만 자동 실행. 수동 실행(workflow_dispatch)은 명시적 의도라 강제.
CONTENT_REFRESH_FORCE: ${{ github.event_name == 'workflow_dispatch' && '1' || '' }}
# vault 원본(OneDrive)이 바뀌면 Watch OneDrive 가 바뀐 경로를 넘겨 디스패치한다 -> 그 목록으로 변경 판단.
# 코드 push 에서는 목록이 없어 스킵. 목록 없이 수동 실행하면 명시적 의도로 보고 강제.
VAULT_CHANGED_FILES: ${{ inputs.vault_changed_files }}
CONTENT_REFRESH_FORCE: ${{ github.event_name == 'workflow_dispatch' && inputs.vault_changed_files == '' && '1' || '' }}
# 빌드 로그(build_log 테이블) 적층용. 없으면 로그 적층만 건너뛰고 리프레시는 정상.
DATABASE_URL: ${{ secrets.DATABASE_URL }}
# 파이프라인 종류별 LLM 동작 토글(contentPipeline). 없으면 코드 기본값으로 폴백(빌드 정상).
Expand All @@ -73,50 +93,13 @@ jobs:
AI_GATEWAY_MODEL: anthropic/claude-opus-4.7
run: npm run content:translate:onepager

# 게이트를 여기서 한 번 더 돌린다. prebuild(Build 스텝)에서도 돌지만, 그건 아래 blob push 와
# PDF 업로드가 끝난 뒤라 위반이 이미 배포된 뒤에야 빌드를 세운다 — PDF 는 Blob 에서 바로
# 게이트를 여기서 한 번 더 돌린다. prebuild(Build 스텝)에서도 돌지만, 그건 아래 OneDrive 게시와
# PDF 업로드가 끝난 뒤라 위반이 이미 배포된 뒤에야 빌드를 세운다 — PDF 는 OneDrive 에서 바로
# 내려가므로 빌드 실패와 무관하게 살아 있다(PRF-149). 재생성 직후·발행 직전이 유일하게
# 유효한 지점이다.
- name: Gate regenerated one-pager (self-designation)
run: npm run check:self-designation

# 생성물(site-data, 원페이저)은 Blob 이 정본이다. 바뀐 것만 info/vault 로 올린다.
# OneDrive 로의 역미러는 hsol-info-blob 동기화 워크플로가 다음 회차에 한다.
- name: Upload regenerated vault outputs to Blob
env:
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
run: |
set -euo pipefail
node --input-type=module -e '
import { readFileSync, existsSync } from "node:fs";
import { put } from "@vercel/blob";
const token = process.env.BLOB_READ_WRITE_TOKEN;
const pulled = new Map(
(existsSync("generated/pulled-generated.sha1") ? readFileSync("generated/pulled-generated.sha1", "utf8") : "")
.split("\n").filter(Boolean).map((l) => { const [h, f] = l.split(" "); return [f, h]; }),
);
const { createHash } = await import("node:crypto");
const files = ["vault/object-views/site-data.json", "vault/object-views/onepager-ko.html", "vault/object-views/onepager-en.html"];
const types = { json: "application/json", html: "text/html; charset=utf-8" };
const changed = [];
for (const f of files) {
const p = `hsol-info-blob/${f}`;
if (!existsSync(p)) continue;
const buf = readFileSync(p);
const h = createHash("sha1").update(buf).digest("hex");
if (pulled.get(f) === h) continue;
await put(`info/${f}`, buf, { access: "private", addRandomSuffix: false, allowOverwrite: true, contentType: types[f.split(".").pop()], token });
changed.push(f);
console.log(`uploaded info/${f}`);
}
if (changed.length) {
const ref = { parentSha: process.env.GITHUB_SHA, parentRef: process.env.GITHUB_REF, workflowRunId: Number(process.env.GITHUB_RUN_ID), recordedAt: new Date().toISOString(), changed };
await put("info/vault/object-views/hsol-info-parent-ref.json", JSON.stringify(ref, null, 2), { access: "private", addRandomSuffix: false, allowOverwrite: true, contentType: "application/json", token });
} else {
console.log("No regenerated vault outputs; skip upload.");
}
'

- name: Upload content refresh failure dumps
if: failure()
uses: actions/upload-artifact@v4
Expand All @@ -128,12 +111,17 @@ jobs:
- name: Generate site.ts from vault
run: npm run content:generate:site-ts

- name: Generate one-pager PDF (html -> pdf -> Blob)
env:
# 없으면 로컬 PDF만 만들고 업로드는 graceful skip(빌드 실패 안 함).
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
- name: Generate one-pager PDF (html -> pdf)
run: npm run content:generate:onepager-pdf

# 사이트는 생성물을 OneDrive 에서 바로 읽는다. 바뀐 것만 vault/object-views 에 쓴다.
- name: Publish generated outputs to OneDrive
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
ONEDRIVE_TOKEN_KEY: ${{ secrets.ONEDRIVE_TOKEN_KEY }}
ONEDRIVE_DRIVE_ID: ${{ secrets.ONEDRIVE_DRIVE_ID }}
run: node ops/scripts/publish-generated.mjs

- name: Lint
run: npm run lint

Expand Down
77 changes: 0 additions & 77 deletions .github/workflows/sync-onedrive-to-blob.yml

This file was deleted.

77 changes: 77 additions & 0 deletions .github/workflows/watch-onedrive.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# hsol.info 콘텐츠의 정본이자 유일한 저장소는 개인 OneDrive 의 hsol-info-blob 폴더다. 사이트는 OneDrive 를 직접 읽는다.
# 이 워크플로는 vault 변경을 감시해 뒤따르는 일을 건다. 코드: ops/scripts/watch-onedrive.mjs
# 기사 : vault/objects/news-articles 가 바뀌면 Neon articles 미러 갱신 + dbId 를 OneDrive 원본에 역기록
# 빌드 : vault 가 바뀌면 Build With Vault Refresh 를 바뀐 경로 목록과 함께 디스패치(site-data, 원페이저 재생성 판단)
# 매시 실행이 OneDrive 리프레시 토큰도 살려 둔다(사이트 런타임도 필요할 때 갱신한다).
#
# Repository secrets
# DATABASE_URL Neon (토큰, 변경 기준선, 기사 미러)
# ONEDRIVE_TOKEN_KEY OneDrive 토큰 암호화 키(32바이트 base64). Vercel env 와 같은 값. 잃으면 재인증 필요
# ONEDRIVE_DRIVE_ID 개인 OneDrive drive id
# ONEDRIVE_BOOTSTRAP_TOKEN (선택) 재인증할 때만. rclone authorize onedrive 출력 JSON

name: Watch OneDrive

on:
schedule:
- cron: "17 * * * *"
workflow_dispatch:
inputs:
dry_run:
description: "기록, 역기록, 디스패치 없이 차이만 출력"
type: boolean
default: false
force_articles:
description: "변경이 없어도 기사 DB 동기화 실행"
type: boolean
default: false

# 기준선과 토큰을 같이 쓰므로 직렬로 돈다.
concurrency:
group: watch-onedrive
cancel-in-progress: false

jobs:
watch:
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
# vault 가 바뀌면 Build With Vault Refresh 를 디스패치한다(GITHUB_TOKEN 의 workflow_dispatch 는 새 실행을 만든다).
actions: write
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci --ignore-scripts --no-audit --no-fund

- name: Install rclone
run: |
curl -fsSL https://rclone.org/install.sh | sudo bash
rclone version | head -1

- name: Watch OneDrive vault
id: watch
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
ONEDRIVE_TOKEN_KEY: ${{ secrets.ONEDRIVE_TOKEN_KEY }}
ONEDRIVE_DRIVE_ID: ${{ secrets.ONEDRIVE_DRIVE_ID }}
ONEDRIVE_BOOTSTRAP_TOKEN: ${{ secrets.ONEDRIVE_BOOTSTRAP_TOKEN }}
FORCE_ARTICLES: ${{ inputs.force_articles && '1' || '' }}
run: node ops/scripts/watch-onedrive.mjs ${{ inputs.dry_run && '--dry-run' || '' }}

# 예전 서브모듈 포인터 이동을 대신하는 신호. vault 원본이 바뀐 회차에만 사이트 생성물 재생성을 건다.
- name: Dispatch site refresh when vault changed
if: ${{ steps.watch.outputs.vault_changed_files != '' && !inputs.dry_run }}
env:
GH_TOKEN: ${{ github.token }}
FILES: ${{ steps.watch.outputs.vault_changed_files }}
run: |
set -euo pipefail
echo "vault 변경 파일: $(echo "$FILES" | tr ',' '\n' | wc -l)개 -> Build With Vault Refresh 디스패치"
gh workflow run build-with-vault-refresh.yml -R "$GITHUB_REPOSITORY" --ref "$GITHUB_REF_NAME" -f vault_changed_files="$FILES"
Loading
Loading