From 5e01c8df4add1de0713770e146962bc27b1b7c7b Mon Sep 17 00:00:00 2001 From: MikiVL Date: Sun, 3 May 2026 01:27:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=AA=E4=BA=BA=E4=B8=BB=E9=A1=B5=20?= =?UTF-8?q?+=20=E9=83=A8=E7=BD=B2=E9=85=8D=E7=BD=AE=EF=BC=88www.mikivl.onl?= =?UTF-8?q?ine=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vite.config.ts: 加 base: '/app/',App 部署在子路径 - server/index.ts: MODELS_FILE 支持环境变量覆盖(容器化写权限) - homepage/index.html: 极简开发者风格个人主页(About/Projects/Skills/Contact) - nginx/default.conf: 反向代理,SSE proxy_buffering off,SPA fallback - docker-compose.yml: Nginx + Hono 容器编排,models_data volume 持久化 - deploy.sh: 一键本地构建 + rsync 上传 + 远端重启 Co-Authored-By: Claude Sonnet 4.6 --- deploy.sh | 51 +++++++ docker-compose.yml | 38 +++++ homepage/index.html | 357 ++++++++++++++++++++++++++++++++++++++++++++ nginx/default.conf | 55 +++++++ server/index.ts | 2 +- src/lib/export.ts | 2 +- vite.config.ts | 1 + 7 files changed, 504 insertions(+), 2 deletions(-) create mode 100755 deploy.sh create mode 100644 docker-compose.yml create mode 100644 homepage/index.html create mode 100644 nginx/default.conf diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..46cda80 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# deploy.sh — 本地运行,构建并上传到 VPS +set -euo pipefail + +VPS_USER="root" +VPS_HOST="your.vps.ip" # ← 替换为你的 VPS IP 或域名 +VPS_DIR="/opt/mikivl" + +info() { printf "\033[36m[INFO]\033[0m %s\n" "$*"; } +ok() { printf "\033[32m[OK]\033[0m %s\n" "$*"; } +die() { printf "\033[31m[ERROR]\033[0m %s\n" "$*" >&2; exit 1; } + +# ── Step 1: 本地构建 ────────────────────────────────────────────────────────── +info "Building Vite app (base=/app/) ..." +npm run build +ok "Build complete → dist/" + +# ── Step 2: 上传文件 ────────────────────────────────────────────────────────── +info "Uploading to ${VPS_USER}@${VPS_HOST}:${VPS_DIR} ..." + +ssh "${VPS_USER}@${VPS_HOST}" "mkdir -p ${VPS_DIR}/{homepage,app,nginx,program1/server,program1/node_modules}" + +# 个人主页 +scp homepage/index.html "${VPS_USER}@${VPS_HOST}:${VPS_DIR}/homepage/" + +# App 构建产物 +rsync -az --delete dist/ "${VPS_USER}@${VPS_HOST}:${VPS_DIR}/app/" + +# 后端源码 +rsync -az server/ "${VPS_USER}@${VPS_HOST}:${VPS_DIR}/program1/server/" +scp package.json package-lock.json "${VPS_USER}@${VPS_HOST}:${VPS_DIR}/program1/" + +# Nginx 配置 + Docker Compose +scp nginx/default.conf "${VPS_USER}@${VPS_HOST}:${VPS_DIR}/nginx/" +scp docker-compose.yml "${VPS_USER}@${VPS_HOST}:${VPS_DIR}/" + +ok "Upload complete." + +# ── Step 3: VPS 上安装依赖并重启服务 ───────────────────────────────────────── +info "Restarting services on VPS ..." + +ssh "${VPS_USER}@${VPS_HOST}" bash <<'REMOTE' +set -euo pipefail +cd /opt/mikivl/program1 +npm ci --omit=dev +cd /opt/mikivl +docker compose up -d --remove-orphans +docker compose ps +REMOTE + +ok "Deployment complete! → https://www.mikivl.online" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7baa322 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +services: + hono-server: + image: node:22-alpine + working_dir: /app + command: npx tsx server/index.ts + environment: + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} + - MODELS_FILE=/app/models_data/models.json + - NODE_ENV=production + volumes: + - ./program1:/app:ro + - models_data:/app/models_data + expose: + - "3001" + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/models"] + interval: 30s + timeout: 5s + retries: 3 + + nginx: + image: nginx:1.27-alpine + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro + - ./homepage:/usr/share/nginx/html/homepage:ro + - ./app:/usr/share/nginx/html/app:ro + - /etc/letsencrypt:/etc/letsencrypt:ro + depends_on: + hono-server: + condition: service_healthy + restart: unless-stopped + +volumes: + models_data: diff --git a/homepage/index.html b/homepage/index.html new file mode 100644 index 0000000..a94274e --- /dev/null +++ b/homepage/index.html @@ -0,0 +1,357 @@ + + + + + + + mikivl — 开发项目 + + + + + + +
+ + +
+

// hello, world

+

mikivl.

+

+ 独立开发者,专注于构建 AI 驱动的生产力工具。
+ 喜欢探索 AI 与实用工具的交叉地带,用代码解决真实问题。 +

+ +
+ +
+ + +
+ +

开发项目

+ +
+ +
+ + +
+ +

技术栈

+
+
+

Frontend

+
    +
  • React / TypeScript
  • +
  • Vite / TailwindCSS
  • +
  • TipTap / ProseMirror
  • +
  • Framer Motion
  • +
+
+
+

Backend

+
    +
  • Node.js / Hono
  • +
  • REST / SSE
  • +
  • Docker / Nginx
  • +
  • Linux / VPS
  • +
+
+
+

AI

+
    +
  • Anthropic Claude API
  • +
  • Prompt Engineering
  • +
  • Streaming (SSE)
  • +
  • RAG / Embeddings
  • +
+
+
+

Tooling

+
    +
  • Git / GitHub
  • +
  • Vitest / ESLint
  • +
  • Claude Code
  • +
  • SSH / rsync
  • +
+
+
+
+ +
+ + +
+ +

联系方式

+ +
+ +
+ + + + + diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..4c6cfa3 --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,55 @@ +server { + listen 80; + server_name www.mikivl.online mikivl.online; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name www.mikivl.online mikivl.online; + + ssl_certificate /etc/letsencrypt/live/www.mikivl.online/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/www.mikivl.online/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_session_cache shared:SSL:10m; + + add_header X-Frame-Options SAMEORIGIN; + add_header X-Content-Type-Options nosniff; + add_header Referrer-Policy strict-origin-when-cross-origin; + + # ── 后端 API(反向代理到 Hono,/api/ 优先匹配)── + location /api/ { + proxy_pass http://hono-server:3001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # SSE 流式响应必须关闭缓冲 + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 300s; + chunked_transfer_encoding on; + } + + # ── 读书笔记 App(/app/ 子路径,SPA)── + location /app/ { + alias /usr/share/nginx/html/app/; + index index.html; + try_files $uri $uri/ /app/index.html; + } + + # ── 个人主页(根路径)── + location / { + root /usr/share/nginx/html/homepage; + index index.html; + try_files $uri $uri/ /index.html; + } + + # 静态资源长缓存 + location ~* \.(js|css|svg|ico|woff2?|png|jpg|webp)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/server/index.ts b/server/index.ts index f53048d..b22dfa3 100644 --- a/server/index.ts +++ b/server/index.ts @@ -10,7 +10,7 @@ const app = new Hono() app.use('*', cors()) // ── Model config persistence ────────────────────────────────────────────────── -const MODELS_FILE = path.resolve('models.json') +const MODELS_FILE = process.env.MODELS_FILE ?? path.resolve('models.json') type ModelConfig = { id: string diff --git a/src/lib/export.ts b/src/lib/export.ts index 51bfd22..9d93c0d 100644 --- a/src/lib/export.ts +++ b/src/lib/export.ts @@ -169,7 +169,7 @@ function nodesToDocxParagraphs(nodes: TipTapNode[]): Paragraph[] { }) case 'codeBlock': { const code = (node.content ?? []).map((n: TipTapNode) => n.text ?? '').join('') - return code.split('\n').map(line => new Paragraph({ + return code.split('\n').map((line: string) => new Paragraph({ children: [new TextRun({ text: line, font: 'Courier New' })], })) } diff --git a/vite.config.ts b/vite.config.ts index ee7de76..14c19dd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,6 +3,7 @@ import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ + base: '/app/', plugins: [react(), tailwindcss()], server: { proxy: {