Yohaku 部署
以下为 Shiro 闭源版本 Yohaku 部署方案,可能随着版本更新而不再适用
安装 1Panel
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
配置镜像输入 y(安装openresty需要),其他一律 enter 即可
安装完成后,打开1panel网址,进入应用商店,安装openresty
后端部署
正常需要安装 docker,不过由于 1Panel 面板默认会安装 docker,所以这里不需要,如果使用宝塔面板或其他方式,请自行安装docker
拉取并修改配置文件
#回到根目录并创建mx-space/core的文件夹,并跳转至core文件夹下
cd && mkdir -p mx-space/core && cd $_
# 拉取 docker-compose.yml 文件
wget https://fastly.jsdelivr.net/gh/mx-space/core@master/docker-compose.yml
主要修改ALLOWED_origins和JWT_SECRET
# Shared env block — mx-migrate boots a full Nest context (it now runs the
# combined schema + app-data migration phases), so it must receive the same
# config the runtime app needs (Redis, snowflake worker id, jwt secret, …).
x-mx-env: &mx-env
TZ: Asia/Shanghai
NODE_ENV: production
REDIS_HOST: redis
PG_HOST: postgres
PG_PORT: "5432"
PG_USER: mx
PG_PASSWORD: mx
PG_DATABASE: mx_core
SNOWFLAKE_WORKER_ID: "1"
ALLOWED_ORIGINS: bambooo.top,localhost:*,127.0.0.1:*
JWT_SECRET: xxxxxxxx
services:
app:
container_name: mx-server
image: innei/mx-server:latest
environment: *mx-env
volumes:
- ./data/mx-space:/root/.mx-space
ports:
- '2333:2333'
depends_on:
mx-migrate:
condition: service_completed_successfully
redis:
condition: service_started
networks:
- mx-space
restart: unless-stopped
healthcheck:
test: [CMD, curl, -f, 'http://127.0.0.1:2333/api/v3/health']
interval: 1m30s
timeout: 30s
retries: 5
start_period: 30s
# One-shot release-phase migration runner. Runs schema migrations (drizzle)
# then app-data migrations (Nest-bootstrapped registry). Exits 0; the `app`
# service waits for completion via `service_completed_successfully` before
# starting. See docs/superpowers/specs/2026-05-05-database-migration-release-phase-design.md.
mx-migrate:
container_name: mx-migrate
image: innei/mx-server:latest
command: ['node', 'migrate.mjs']
environment: *mx-env
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- mx-space
restart: 'no'
postgres:
container_name: postgres
image: postgres:16-alpine
environment:
- POSTGRES_USER=mx
- POSTGRES_PASSWORD=mx
- POSTGRES_DB=mx_core
volumes:
- ./data/postgres:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U mx -d mx_core']
interval: 30s
timeout: 5s
retries: 5
start_period: 10s
networks:
- mx-space
restart: unless-stopped
redis:
image: redis:alpine
container_name: redis
volumes:
- ./data/redis:/data
healthcheck:
test: [CMD-SHELL, 'redis-cli ping | grep PONG']
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
networks:
- mx-space
restart: unless-stopped
networks:
mx-space:
driver: bridge
启动 core
docker compose up -d
这里需要几分钟时间,耐心等待
网站创建
以下步骤均在 1Panel 中进行
证书申请
先创建Acme账户,账号类型建议选ZeroSSL,省事
创建完后申请证书 → 填写主域名 → Acme账户 → 秘钥算法(EC 256) → 验证方式(HTTP) → 自动续签 → 确认
创建网站
创建 → 静态网站 → 监听IPV6 → 配置 → HTTPS(开启,选择刚才的证书) → 配置文件(将下面内容插入到中间)
我是单域名配置,如果你是双域名,参考官方文档
# WebSocket 地址
location /socket.io {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:2333/socket.io;
}
# API 地址
location /api/v3 {
proxy_pass http://127.0.0.1:2333/api/v3;
}
# 简读 render 地址
location /render {
proxy_pass http://127.0.0.1:2333/render;
}
# Shiro 地址
location / {
proxy_pass http://127.0.0.1:2323;
}
# 后台地址
location /proxy {
proxy_pass http://127.0.0.1:2333/proxy;
}
location /qaqdmin { #如果需要修改自己的后台地址可以改这里
proxy_pass http://127.0.0.1:2333/proxy/qaqdmin;
}
修改完毕,点击保存并重载
访问 https://你的域名/qaqdmin 就可以正常进入博客后台(如果碰到问题也不要急,可以在评论区留下你的问题,或许可以给你提供一些帮助),然后就是简单的填写博客基本信息
前端部署
这一步中间可能会出现各种错误,建议在服务器上安装 Claude Code,让大模型帮你分析排查问题,一般都能解决
我采用的是 Innei 提供的自动部署工作流方式
Fork
打开github,fork上面的仓库
设置 Secrets
打开仓库,进入 Settings → Secrets and variables → Actions → Repository secrets → 挨个添加下面的参数
HOST服务器地址USER服务器用户名PASSWORD服务器密码PORT服务器 SSH 端口KEY服务器 SSH Key(可选,密码 key 二选一)GH_PAT可访问之前 repository 内填写仓库的 Github Token
配置 .env
在服务器 root 下创建 yohaku 文件夹,并在其下创建 .env
cd root/
mkdir yohaku
cd yohaku/
touch .env
vim .env
填写以下内容
BASE_URL=
NEXT_PUBLIC_API_URL=
NEXT_PUBLIC_CLIENT_API_URL=
NEXT_PUBLIC_GATEWAY_URL=
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
TMDB_API_KEY=
GH_TOKEN=
API_URL=
修改 deploy
打开 .github/workflows/deploy.yml 进行修改
name: Build and Deploy
on:
push:
branches:
- main
# schedule:
# - cron: '0 3 * * *'
repository_dispatch:
types: [trigger-workflow]
permissions: write-all
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
HASH_FILE: build_hash
SOURCE_REPO: innei-dev/Yohaku
BUILD_COMMAND: pnpm --filter @yohaku/web build:ci
STANDALONE_SUBPATH: standalone/apps/web
jobs:
prepare:
name: Prepare
runs-on: ubuntu-latest
if: ${{ github.event.head_commit.message != 'Update hash file' }}
outputs:
hash_content: ${{ steps.read_hash.outputs.hash_content }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Read HASH_FILE content
id: read_hash
run: |
content=$(cat ${{ env.HASH_FILE }}) || true
echo "hash_content=$content" >> "$GITHUB_OUTPUT"
check:
name: Check Should Rebuild
runs-on: ubuntu-latest
needs: prepare
outputs:
canceled: ${{ steps.use_content.outputs.canceled }}
steps:
- uses: actions/checkout@v4
with:
repository: innei-dev/Yohaku
token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
fetch-depth: 0
lfs: true
submodules: recursive
- name: Use content from prev job and compare
id: use_content
env:
FILE_HASH: ${{ needs.prepare.outputs.hash_content }}
run: |
file_hash=$FILE_HASH
current_hash=$(git rev-parse --short HEAD)
echo "File Hash: $file_hash"
echo "Current Git Hash: $current_hash"
if [ "$file_hash" == "$current_hash" ]; then
echo "Hashes match. Stopping workflow."
echo "canceled=true" >> $GITHUB_OUTPUT
else
echo "Hashes do not match. Continuing workflow."
fi
build:
name: Build artifact
runs-on: ubuntu-latest
needs: check
if: ${{needs.check.outputs.canceled != 'true'}}
strategy:
matrix:
node-version: [lts/*]
outputs:
sha_short: ${{ steps.store.outputs.sha_short }}
branch: ${{ steps.store.outputs.branch }}
steps:
- uses: actions/checkout@v4
with:
repository: ${{ env.SOURCE_REPO }}
token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
fetch-depth: 0
lfs: true
submodules: recursive
- name: Checkout LFS objects
run: git lfs checkout
- uses: pnpm/action-setup@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- uses: jongwooo/next-cache@v1
- name: Install dependencies
run: pnpm install
- name: Build project
env:
BASE_URL: ${{ secrets.BASE_URL }}
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
NEXT_PUBLIC_GATEWAY_URL: ${{ secrets.NEXT_PUBLIC_GATEWAY_URL }}
run: |
${{ env.BUILD_COMMAND }}
cd apps/web/.next
rm -rf cache
cp -r ../public ./standalone/public
mv ./static ./standalone/apps/web/.next/static
cd ./standalone
echo ';process.title = "Yohaku (NextJS)"' >> server.js
cd "$GITHUB_WORKSPACE"
mkdir -p assets
rm -f assets/release.zip
(cd apps/web/.next && zip --symlinks -r "$GITHUB_WORKSPACE/assets/release.zip" ./* -x "dev/*" -x "cache/*")
# - uses: actions/upload-artifact@v4
# with:
# name: dist
# path: assets/release.zip
# retention-days: 7
- name: Cache Build Artifacts
id: cache-primes
uses: actions/cache/save@v4
with:
path: assets
key: ${{ github.run_number }}-release
- name: Store artifact commit version
shell: bash
id: store
run: |
sha_short=$(git rev-parse --short HEAD)
branch_name=$(git rev-parse --abbrev-ref HEAD)
echo "sha_short=$sha_short" >> "$GITHUB_OUTPUT"
echo "branch=$branch_name" >> "$GITHUB_OUTPUT"
deploy:
name: Deploy artifact
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- uses: actions/cache/restore@v4
with:
path: assets
key: ${{ github.run_number }}-release
- name: Restore cached Build Artifacts
id: cache-primes-restore
uses: actions/cache/restore@v4
with:
path: |
assets
key: ${{ github.run_number }}-release
- name: Move assets to root
run: mv assets/release.zip release.zip
- name: Prepare standalone PM2 config
run: cp pm2/ecosystem.config.js ecosystem.config.js
- name: copy file via ssh password
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USER }}
password: ${{ secrets.PASSWORD }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
source: 'release.zip'
target: '/tmp/yohaku'
- name: copy PM2 config via ssh password
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USER }}
password: ${{ secrets.PASSWORD }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
source: 'ecosystem.config.js'
target: '/tmp/yohaku'
- name: Exec deploy script with SSH
uses: appleboy/ssh-action@master
with:
command_timeout: 5m
host: ${{ secrets.HOST }}
username: ${{ secrets.USER }}
password: ${{ secrets.PASSWORD }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
script: |
set -e
source $HOME/.bashrc
basedir=$HOME/yohaku
workdir=$basedir/${{ github.run_number }}
mkdir -p $workdir
mkdir -p $basedir/.cache
mv /tmp/yohaku/release.zip $workdir/release.zip
install -m 644 /tmp/yohaku/ecosystem.config.js $basedir/ecosystem.config.js
cd $workdir
unzip -qq -o $workdir/release.zip
rm -r /tmp/yohaku
rm -rf $workdir/${{ env.STANDALONE_SUBPATH }}/.env
ln -s $HOME/yohaku/.env $workdir/${{ env.STANDALONE_SUBPATH }}/.env
export NEXT_SHARP_PATH=$(npm root -g)/sharp
# https://github.com/Unitech/pm2/issues/3054
# symlink workdir node entry file to basedir
ln -sf $workdir/${{ env.STANDALONE_SUBPATH }}/server.js $basedir/server.js
mkdir -p $workdir/${{ env.STANDALONE_SUBPATH }}/.next
rm -rf $workdir/${{ env.STANDALONE_SUBPATH }}/.next/cache
ln -sf $basedir/.cache $workdir/${{ env.STANDALONE_SUBPATH }}/.next/cache
cd $basedir
if pm2 describe Yohaku >/dev/null 2>&1; then
pm2 reload ecosystem.config.js --update-env
else
pm2 start ecosystem.config.js --update-env
fi
rm $workdir/release.zip
pm2 save
echo "Deployed successfully"
- name: After deploy script
run: |
hash=${{ needs.build.outputs.sha_short }}
# curl -X "POST" "https://mx.innei.in/api/v2/fn/shiro/new-version-hook" -H 'Content-Type: application/json' -d "{\"hash\": \"$hash\", \"key\": \"\"}"
${{ secrets.AFTER_DEPLOY_SCRIPT }}
store:
name: Store artifact commit version
runs-on: ubuntu-latest
needs: [deploy, build]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
# Get the commit version from the build job
- name: Use outputs from build
env:
SHA_SHORT: ${{ needs.build.outputs.sha_short }}
BRANCH: ${{ needs.build.outputs.branch }}
run: |
echo "SHA Short from build: $SHA_SHORT"
echo "Branch from build: $BRANCH"
- name: Write hash to file
env:
SHA_SHORT: ${{ needs.build.outputs.sha_short }}
run: echo $SHA_SHORT > ${{ env.HASH_FILE }}
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add ${{ env.HASH_FILE }}
git commit -a -m "Update hash file"
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
如果直接使用 Innei 的部署过程中出现报错,可以参考我的进行修改,或者询问大模型,github自带的 Copilot 也挺好用的
改完之后 commit,会自动跑,可以进入Actions查看流程
配置云函数
进入博客后台,附加功能 → 配置与云函数
创建一条 theme 引用、名称为 shiro 的配置项(数据类型 JSON 或 YAML),这里贴上官方提供的案例
{
"footer": {
"otherInfo": {
"date": "2020-{{now}}",
"icp": {
"text": "萌 ICP 备 20236136 号",
"link": "https://icp.gov.moe/?keyword=20236136"
}
},
"linkSections": [
{
"name": "关于",
"links": [
{ "name": "关于本站", "href": "/about-site" },
{ "name": "关于我", "href": "/about" },
{
"name": "关于此项目",
"href": "https://github.com/Innei/Yohaku",
"external": true
}
]
},
{
"name": "更多",
"links": [
{ "name": "时间线", "href": "/timeline" },
{ "name": "友链", "href": "/friends" }
]
},
{
"name": "联系",
"links": [
{ "name": "写留言", "href": "/message" },
{ "name": "GitHub", "href": "https://github.com/innei", "external": true }
]
}
]
},
"config": {
"color": {
"light": ["#33A6B8", "#FF6666", "#26A69A", "#fb7287", "#69a6cc"],
"dark": ["#F596AA", "#A0A7D4", "#ff7b7b", "#99D8CF", "#838BC6"]
},
"site": {
"favicon": "/favicon.svg",
"faviconDark": "/favicon-dark.svg"
},
"hero": {
"title": {
"template": [
{
"type": "span",
"text": "Hi, I'm ",
"style": { "fontWeight": 300, "opacity": 0.85 }
},
{
"type": "span",
"text": "Innei",
"style": {
"fontWeight": 500,
"color": "var(--color-accent)",
"letterSpacing": "-0.02em"
}
},
{
"type": "span",
"text": " 👋",
"style": {
"fontWeight": 300,
"display": "inline-block",
"transform": "rotate(-8deg)"
}
},
{ "type": "br" },
{
"type": "span",
"text": "A NodeJS Full Stack ",
"style": { "fontWeight": 300, "opacity": 0.8 }
},
{
"type": "code",
"text": "<Developer />",
"style": {
"display": "inline-block",
"fontFamily": "var(--font-mono)",
"fontSize": "0.72em",
"fontWeight": 500,
"padding": "0.25em 0.55em",
"borderRadius": "0.35em",
"backgroundColor": "color-mix(in srgb, var(--color-accent) 10%, transparent)",
"color": "var(--color-accent)",
"border": "1px solid color-mix(in srgb, var(--color-accent) 22%, transparent)"
}
},
{
"type": "span",
"style": {
"display": "inline-block",
"width": "2px",
"height": "0.9em",
"backgroundColor": "var(--color-accent)",
"marginLeft": "2px",
"animation": "blink 1.2s linear infinite"
}
}
]
},
"description": "An independent developer coding with love."
},
"module": {
"activity": {
"enable": true,
"endpoint": "/fn/ps/update"
},
"donate": {
"enable": false,
"link": "",
"qrcode": []
},
"bilibili": {
"liveId": 0
},
"openpanel": {
"enable": false,
"id": "",
"url": ""
},
"posts": {
"mode": "loose"
},
"signature": {
"svg": "",
"animated": true
}
}
}
}
修改好后保存
都完成后,访问前端,没问题的话,恭喜你部署成功