Static website structure clone for engineering practice and delivery workflow hardening.
- Purpose: maintain a static site that mirrors information architecture and interaction behavior for testing and deployment rehearsal.
- Constraint: this repository is structure clone only. Do not copy proprietary or copyrighted source content from external sites.
- Constraint: keep visible site content stable unless a change request explicitly asks for content updates.
- Mechanism: all post source content is Markdown-only (
content/posts/*.md, local data, not versioned in Git); public HTML is generated from Markdown and should not be edited manually.
.
├── admin/
│ ├── public/
│ └── server.mjs
├── assets/
│ ├── hero.svg
│ ├── main.js
│ ├── post-renderer.js
│ └── style.css
├── content/
│ └── posts/*.md # local source of truth (gitignored)
├── docs/
│ ├── ARCHITECTURE.md
│ ├── DEVELOPMENT.md
│ ├── DEPLOYMENT.md
│ └── RELEASE.md
├── scripts/
│ ├── build-site.mjs
│ ├── migrate-html-to-md.mjs
│ ├── convert-posts-html-to-markdown.mjs
│ ├── site-lib.mjs
│ ├── check-links.mjs
│ ├── check-metadata.mjs
│ ├── check-top-btn.mjs
│ ├── check-markdown-source.mjs
│ └── post-alias-audit.mjs
├── CONTRIBUTING.md
└── package.json
# Generated at build time (gitignored)
# - index.html
# - about/ categories/ list/ tags/
# - posts/<slug>/index.html
Requirements:
- Node.js 18+
- Python 3 (for static server)
Install:
npm installBuild static pages from markdown source:
npm run build:siteIf you import legacy HTML content once, run:
npm run content:migrate
npm run content:normalizeServe locally:
npm run serveOr run on a random free port:
npm run serve:random- Edit posts only in
content/posts/*.md(local data; not committed). - Frontmatter fields:
slug,title,date,status,category,tags,summary. npm run build:sitegenerates page shells (posts/*,index,list,categories,tags).- Post 正文 Markdown 在用户浏览器中渲染(
assets/post-renderer.js+markdown-it)。 - Post 页面代码块使用
highlight.js+ GitHub 风格主题进行客户端语法高亮。 - 生成的 HTML 只是构建产物,默认 不入库(已在
.gitignore中忽略)。 npm run check:markdown-sourceensures post bodies do not contain raw HTML tags.
npm run check:links
npm run check:top-btn
npm run check:metadata
npm run check:post-alias
npm run check:markdown-source
npm run qaRun the admin backend:
ADMIN_PASSWORD=change-me-now ADMIN_HOST=127.0.0.1 ADMIN_PORT=59051 npm run adminThen open http://127.0.0.1:59051/admin.
Admin API/UI behavior:
- Uses
content/posts/*.mdas source of truth (Markdown-only body content, local-only data). - Post frontmatter supports
status: "published" | "draft"; missing status defaults topublished. - Static generation (
posts/*, homepage/list/categories/tags, numeric aliases) includes onlypublishedposts. - Create/edit/delete operations rewrite markdown and rebuild static pages.
- Admin editor includes a markdown toolbar and image upload button.
- Image upload endpoint: authenticated
POST /admin/api/upload-imagewith multipart fieldimage, returning/assets/uploads/<filename>. - Category registry is persisted in
content/categories.json; startup auto-merges categories found in existing markdown posts. - Category management API (authenticated):
GET /admin/api/categoriesreturns{ categories: [{ name, count }] }POST /admin/api/categorieswith body{ name }creates a categoryDELETE /admin/api/categories/:namewith optional body{ reassignTo }migrates posts (default未分类) then deletes the category
- Admin supports API Token management (
/admin/api/agent-tokens) for external agent write access:GET /admin/api/agent-tokenslist token metadataPOST /admin/api/agent-tokenswith{ name }create token (plaintext shown once)DELETE /admin/api/agent-tokens/:idrevoke token
- Swagger 页面:
/api/docs - OpenAPI 文档:
/api/v1/openapi.json
Read operations are public, write operations require token.
For each published post page URL (/posts/<slug>/), machine-readable mirrors are available:
-
Query mirror on same URL:
/posts/<slug>/?format=raw→ markdown source/posts/<slug>/?format=json→ JSON payload
-
Extension mirrors:
/posts/<slug>.md→ markdown source/posts/<slug>.json→ JSON payload
-
Read endpoints:
GET /api/v1/healthGET /api/v1/posts?status=published|draft|all&includeContent=1&q=keywordGET /api/v1/posts/:slugGET /api/v1/categories?status=published|draft|allGET /api/v1/tags?status=published|draft|allGET /api/v1/taxonomy?status=published|draft|all
-
Write endpoints (Bearer token required):
POST /api/v1/postsPUT /api/v1/posts/:slugDELETE /api/v1/posts/:slugPOST /api/v1/categoriesDELETE /api/v1/categories/:namePOST /api/v1/tags/renameDELETE /api/v1/tags/:name
Token usage:
curl -X POST http://127.0.0.1:59051/api/v1/posts \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"slug":"demo-post","title":"Demo","date":"2026-02-24","status":"published","category":"github trend","tags":["demo"],"summary":"summary","content":"# hello"}'Security note:
- Change
ADMIN_PASSWORDbefore exposing the service anywhere. - Never commit or share plaintext API tokens.
Script: scripts/daily_release_backup.sh
What it does:
- Creates a full-site snapshot (code + local data) from the project working tree
- Compresses it as
tar.gz, then encrypts with AES-256 (openssl) - Encrypts backup artifact with a daily-rotated password (distributed via private channel)
- Publishes/updates GitHub release tag
backup-YYYYMMDDand uploads asset - Deletes old
backup-*releases older than 30 days (--cleanup-tag)
Manual run:
cd /home/ubuntu/.openclaw/workspace-liuyun/projects/opflow-website
./scripts/daily_release_backup.shGitHub Actions workflow: .github/workflows/ci.yml.
- Triggers on
pushandpull_requesttomainandmaster. - Uses
actions/checkout@v4andactions/setup-node@v4with Node.js22. - Installs dependencies with
npm install --no-package-lock --registry=https://registry.npmjs.org --no-audit --no-fundand runsnpm run qa.
Recommended rollout sequence:
- Deploy and validate on a random high port first (for example
58050) to avoid impacting live traffic. - Run QA checks and manual smoke tests against that port.
- Migrate serving to port
80only after validation passes. - Keep rollback path simple (previous build + previous port mapping).
Detailed process is documented in docs/DEPLOYMENT.md.
Generated pages append a deterministic ?v=<timestamp-pair> query to assets/style.css and assets/main.js for cache busting.