Record of Deploying Yohaku as a Personal Space
In the past couple of days, I visited Quiet Forest and noticed that its theme Yohaku perfectly matches my personal aesthetic. Compared to my previous Astral Reverie, which was more feature-rich but less refined, Yohaku is better suited for life records and personal space in terms of the depth of text creation. Its representation forms also cover personal trivial matters, learning, and inspiration needs. Since this is my first contact with modern frontend projects, and before this I primarily used PHP along with native CSS and JS, I encountered many issues of varying sizes when deploying Yohaku. Therefore, I am writing this article to document the process for future reference.
Since I already have a cloud server with a public IPv4 address, this article mainly introduces deployment on a Linux cloud server (with Baota panel). The deployment scheme based on Vercel is not covered in this article. If needed, please refer to other articles on the internet.
References
Related project repository:
Related documentation, technical instructions, and guides referenced in this article:
Important
The Yohaku project is a closed-source project, so Innei's Yohaku repository on GitHub is a private repository. If you need access to this repository, you must sponsor the developer through GitHub Sponsor.
https://github.com/sponsors/Innei
Environment Deployment and Requirements
The main body of the Yohaku project is on GitHub. It first needs to be compiled and built before being deployed to the server. Since Node.js projects have high performance overhead during compilation, this article adopts the method of using a GitHub Action workflow.
Domain Registration and DNS Configuration
Currently, mainstream domain registration channels include Tencent Cloud, Alibaba Cloud, and Baidu Cloud in China, and Cloudflare, GoDaddy, and DynaDot abroad. I personally recommend using Cloudflare for registration, or you can choose your preferred domain registrar—there is no difference.
After registering the domain, go to the "DNS Resolution" management page of your domain registrar (note: Tencent Cloud's domain management is done through DNSPod), and add the following A records, filling in the server's public IP as the resolution address:
- @
- api
Note
Resolution records refer to your domain's third-level, fourth-level, or higher domain names. For example, if you resolve
example.comto address1.1.1.1with record nameapi, the DNS server will resolveapi.example.comto1.1.1.1. Note that@represents the root of the domain, so the record@represents the domain itself.
Server Environment and Account Registration
First, we need to prepare a server with public internet access and an independent domain name. Taking Tencent Cloud as an example, a cloud server typically costs between CNY 30 and CNY 70 per month, and a domain name costs as little as CNY 10 per year. These resources are not expensive for building a website—in fact, they are quite cost-effective. In addition to these hardware requirements, we also need the following software prerequisites:
- A GitHub account (register here)
- Deploy the following runtime environments on the server
- Docker
- Node.js 20.11 or newer (special note: this software cannot run on CentOS 7 environment on older servers)
- pnpm
- pm2
- sharp
Additionally, based on the various functionality implementations of Yohaku, the following software is recommended:
-
Clerk account(since Core 7, built-in OAuth 2.0 is used to implement corresponding functions) - TMDB account
For configuring the Node.js environment on the server, you can refer to the following commands:
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo dnf install -y nodejs
# Verify
node -v
npm install -g pnpm
# Verify
pnpm -v
npm install -g pm2
# Verify
pm2 -v
cd && mkdir -p mx-space/core && cd $_
pnpm add sharp
If you installed Node.js using the Baota panel's Node.js manager and cannot find Node.js in the terminal, you can check the installation path displayed by the manager and add it to your PATH.
For the Docker environment configuration on the server, Tencent Cloud's Baota application server usually comes with Docker pre-installed, and the 1Panel panel also comes with Docker. You can check if Docker is installed by entering docker -v in the terminal. If the terminal returns a version number, it means Docker is installed on the server.
If needed, you can refer to the following terminal commands to install Docker:
# If your server is in China, it is recommended to use Alibaba Cloud's installation script
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
# If abroad, you can use the official script directly
curl -fsSL https://get.docker.com | bash -s docker
# Check version to verify installation
docker -v
Backend Service Configuration
Deploy MixSpace
Tip
For this part, you can get a more detailed explanation from the official documentation: https://mx-space.js.org/docs/core/docker
The Yohaku project is a front-end/back-end separated project that relies on the MixSpace backend. Therefore, we first need to deploy MixSpace. Using the easier Docker approach, we first pull the docker-compose.yml for deploying the backend:
cd && mkdir -p mx-space/core && cd $_
# Pull the docker-compose.yml file
wget https://fastly.jsdelivr.net/gh/mx-space/core@master/docker-compose.yml
Fill in the following required information in the environment field of docker-compose.yml:
- JWT_SECRET=
- ALLOWED_ORIGINS=
Explanation for these two fields:
JWT Secret: A string with a length of no less than 16 characters and no more than 32 characters, used to encrypt the user's JWT. Be sure to keep your secret safe and do not disclose it to anyone.Allowed Origins: The allowed domain names. Fill in the domain names you configured earlier in DNS, separated by English commas. Note that the filled-in domain names should be complete and without prefixes. For example:api.example.com,example.com, not like:https://api.example.comor justapior even@.
Start Core:
docker compose up -d
Configure Reverse Proxy
Taking the Baota panel as an example, open the Baota panel, go to Website, create a new website, fill in the domain as api.your-domain, leave the rest unchanged, and click create. At this point, try accessing your domain. If it displays "Website created successfully", it means your configuration is correct.
Click Settings on the right side of your newly created website project, find SSL on the left side of the popup, configure a certificate (e.g., Let's Encrypt free certificate), and enable HTTPS.
Then repeat the above steps to create another website with the domain as your-domain.
In the Settings of your backend website, under Reverse Proxy, click Add Reverse Proxy, set Target URL to http://127.0.0.1:2333, set Send Domain to $host, and click OK.
In the Settings of your frontend website, under Reverse Proxy, click Add Reverse Proxy, set Target URL to http://127.0.0.1:2323, set Send Domain to $host, and click OK.
Then go to https://api.your-domain/proxy/qaqdmin to configure the backend server.
Troubleshooting
If you encounter various unexpected issues, I recommend asking an AI Agent.
In this new era, we are in a time of booming artificial intelligence. So when you encounter a problem, please first consult various AI assistant programs; they will help you resolve most of your questions.
I will only list one issue I encountered here: I previously had Cloudreve configured on the server, so there was already a container named redis in Docker. In this case, you need to rename the relevant container in docker-compose.yml to deploy it.
Updating MixSpace
If the major version number is the same (e.g., 11.0.6 -> 11.6.1 or 11.0.6 -> 11.0.8, as long as the number before the first decimal point has not changed, the major version is the same), updating MixSpace is very simple:
- Enter the MixSpace Core deployment directory;
- Directly
pullthe new version; - Bring the new version online.
cd ./mx-space/core
docker compose pull app
docker compose up -d app
After seeing the Started prompt, open your backend page and perform an update check:
- The article list displays normally;
- Comments and content associated with articles display normally;
- Backend login works normally;
- Send an article, then delete it, to test whether articles can be published and deleted normally.
Frontend Yohaku Deployment
I used the GitHub Actions provided by @Innei for pre-building and deployment.
Tip
For this part, if you have some experience, you can refer to the developer's official documentation:
Fork the Deployment Workflow Repository
First, you need to Fork a copy of the deploy action repository from the author's repository to your GitHub account.
Note
"Fork" on GitHub essentially means "legally copying homework". The author has written a template repository for an "automated deployment workflow". You need to fine-tune some parameters of this workflow according to your server and domain needs, but you cannot directly modify their code. So you need to click the Fork button to copy the entire repository unchanged to your own GitHub account. Then the repository becomes yours, and you can do whatever you want with it.
- Log in to your GitHub account.
- Open the author's workflow repository address: yohaku-deploy-action
- Look at the upper right corner of the screen, find a button called Fork (usually with a small number next to it), and click it!
- A "Create a new fork" confirmation page will appear. You don't need to change anything; just scroll to the bottom and click the green Create fork button.
- Wait two or three seconds. After the page refreshes, you will see that the repository name in the upper left has changed to
your-username/yohaku-deploy-action.
Obtain GH_PAT
Although it is a feature under GitHub, GitHub Actions itself does not have permission to view the code of private repositories under an account. Therefore, you need to configure an API key (i.e., Personal Access Token (PAT)) for the account so that GitHub Actions can access the repository.
On any GitHub page, click your profile avatar in the upper right corner, and select Settings from the dropdown menu at the bottom.
In the long menu on the left, scroll to the very bottom and click Developer settings.
In the left menu, select Personal access tokens -> then select Tokens (classic).
In the upper right corner of the page, click Generate new token -> select Generate new token (classic). (You may need to enter your GitHub password to verify your identity.)
On the new token creation page:
- Note: Fill in anything, e.g.,
Deploy-Shiro. - Expiration: Select
No expiration(never expires) to avoid having to redo it later. - Select scopes: Find the
repooption and check the large checkbox next to it (indicating this token can access your private repositories).
- Note: Fill in anything, e.g.,
Scroll to the bottom and click the green Generate token.
Important
The page will display a long string of random characters (starting with
ghp_). Immediately copy it and save it to your local notepad! This password is only shown once; if you refresh the page, it will never be seen again.
Configure Secrets
Next, fill in the account's API key and other necessary information into the Deploy repository's configuration:
- Go back to the repository page you forked in the first step (
your-username/yohaku-deploy-action). - Click the Settings tab at the top right of the repository.
- In the left menu, find and click: Secrets and variables -> Actions.
- Click the green New repository secret button.
- On the fill-in page:
- Name: Strictly fill in
GH_PAT(all uppercase, must be exactly this). - Secret: Paste the long
ghp_token you saved in the notepad in step 2.
- Name: Strictly fill in
- Click Add secret.
Continue clicking New repository secret to add all of the following:
HOST: Fill in your server IP (e.g.,1.1.1.1)USER: Fill inrootor your usernamePASSWORD: Fill in your server root password (if insecure, you can useKEYto fill in an SSH key; choose one, password is more suitable for beginners)PORT: Fill in22(if you haven't changed your SSH port)BASE_URL: Fill inhttps://your-domainNEXT_PUBLIC_API_URL: Fill inhttps://api.your-domain/api/v3NEXT_PUBLIC_GATEWAY_URL: Fill inhttps://api.your-domain
Note
Make sure that port
22is exposed in the firewall of the Baota panel and your service provider's cloud server dashboard.
Configure the .env Environment Variable File
Create a folder named shiro in the server's /~ directory, then create a file named .env:
cd && mkdir -p shiro && cd $_ && touch .env
Fill in the following content in .env, filling in the corresponding values in the blanks:
Tip
Although according to the developer's official statement, you should set
NEXT_PUBLIC_GATEWAY_URL=${BASE_URL}andNEXT_PUBLIC_API_URL={BASE_URL}/api/v3.However, Next.js sometimes has trouble parsing dynamic concatenation like
${BASE_URL}. To avoid giving it a chance to throw errors, I recommend hardcoding the full URLs.
BASE_URL=
NEXT_PUBLIC_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=
1. Base URL Part (hardcoding is safer)
# Your website's main domain
BASE_URL=https://your-domain
# Backend API address (hardcode the full URL)
NEXT_PUBLIC_API_URL=https://api.your-domain/api/v3
# Gateway address
NEXT_PUBLIC_GATEWAY_URL=https://api.your-domain
2. Clerk Authentication (Website backend login)
This is a third-party service for login.
- Go to the Clerk official website to register an account and create a new application.
- Then find these two keys in its console and fill them in:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxx (Fill in the public key obtained from Clerk)
CLERK_SECRET_KEY=sk_test_xxxxxxxxxxx (Fill in the secret key obtained from Clerk)
# The following four are login redirect routes; keep them unchanged!
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=/
3. Other Miscellaneous APIs
TMDB_API_KEY=
GH_TOKEN=ghp_xxxxxxxxxxxxxxxxx
TMDB_API_KEY: TMDB is a movie database. Yohaku blog supports displaying movie cards. If you don't want to deal with it for now, leave it blank (the movie page may have no data, but the overall site will still work). If you want to do it, go to themoviedb.org to apply for an API key and fill it in.GH_TOKEN: Token for GitHub dynamic display. You can directly paste the longghp_"master key" you obtained in the previous step here.
GitHub Actions
Next, go to the Actions page of the repository and let GitHub Actions pre-build the project.
- Click the Actions tab at the top.
- If you see a prompt saying "I understand my workflows, go ahead and enable them", click the green button to enable them.
- In the left menu, click the workflow name Build and Deploy.
- On the right, find a button that says Run workflow, click it, then click the green Run workflow that appears.
If there is no Run workflow button, go into the .github/workflows/ directory, open deploy.yml, click the edit (pencil) button in the upper right, then click Commit changes.
Note
When you click save (Commit), the system will consider that you have updated the code (triggering a push), and then it will automatically start Build and Deploy.
Troubleshooting
If there are errors when using GitHub Action to build Yohaku, and the log looks like:
0s
Run sh ./ci-release-build.sh
sh ./ci-release-build.sh
shell: /usr/bin/bash -e {0}
env:
HASH_FILE: build_hash
PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
BASE_URL: ***
NEXT_PUBLIC_API_URL: ***
NEXT_PUBLIC_GATEWAY_URL: ***
No projects matched the filters in "/home/runner/work/shiroi-deploy-action/shiroi-deploy-action"
./ci-release-build.sh: 8: cd: can't cd to apps/web/.next
Error: Process completed with exit code 2.
You can try making the following modifications to deploy.yml:
Caution
This method is a personal revision based on error analysis and AI Agent suggestions. There may be some undiscovered bugs. If you encounter similar issues, you can try contacting the author or submitting an issue on GitHub.
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:
# - name: Download artifact
# uses: actions/download-artifact@v4
# with:
# name: dist
- 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: Create PM2 config file
run: |
cat > ecosystem.config.js << 'EOF'
module.exports = {
apps: [
{
name: 'Shiroi',
script: './server.js',
cwd: __dirname,
exec_mode: 'fork',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '180M',
env: {
NODE_ENV: 'production',
HOSTNAME: '0.0.0.0',
PORT: process.env.PORT || 2323,
},
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
},
],
};
EOF
- name: copy file via ssh password
uses: appleboy/[email protected]
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/[email protected]
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 Shiroi >/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 }}
Configuration in MixSpace
Note
Official documentation related to this part:
Enter the Mix Space backend, go to the "Configuration & Cloud Functions" page, click the add button in the upper right corner, and fill in the following settings in the editing page:
- Name:
shiro - Reference:
theme - Data type:
JSON - Data (using the official example data as a template):
{
"footer": {
"otherInfo": {
"date": "2020-{{now}}",
"icp": {
"text": "Moe ICP Record 20236136",
"link": "https://icp.gov.moe/?keyword=20236136"
}
},
"linkSections": [
{
"name": "About",
"links": [
{
"name": "About Site",
"href": "/about-site"
},
{
"name": "About Me",
"href": "/about"
},
{
"name": "About This Project",
"href": "https://github.com/innei/Shiro",
"external": true
}
]
},
{
"name": "More",
"links": [
{
"name": "Timeline",
"href": "/timeline"
},
{
"name": "Friends",
"href": "/friends"
},
{
"name": "Status",
"href": "https://status.innei.in/status/main",
"external": true
}
]
},
{
"name": "Contact",
"links": [
{
"name": "Leave a Message",
"href": "/message"
},
{
"name": "Send Email",
"href": "mailto:[email protected]",
"external": true
},
{
"name": "GitHub",
"href": "https://github.com/innei",
"external": true
}
]
}
]
},
"config": {
"color": {
"light": [
"#33A6B8",
"#FF6666",
"#26A69A",
"#fb7287",
"#69a6cc",
"#F11A7B",
"#78C1F3",
"#FF6666",
"#7ACDF6"
],
"dark": [
"#F596AA",
"#A0A7D4",
"#ff7b7b",
"#99D8CF",
"#838BC6",
"#FFE5AD",
"#9BE8D8",
"#A1CCD1",
"#EAAEBA"
]
},
"bg": [
"/static/images/F0q8mwwaIAEtird.jpeg",
"/static/images/IMG_2111.jpeg.webp.jpg"
],
"custom": {
"css": [],
"styles": [],
"js": [],
"scripts": []
},
"site": {
"favicon": "/innei.svg",
"faviconDark": "/innei-dark.svg"
},
"hero": {
"title": {
"template": [
{
"type": "h1",
"text": "Hi, I'm ",
"class": "font-light text-4xl"
},
{
"type": "h1",
"text": "Innei",
"class": "font-medium mx-2 text-4xl"
},
{
"type": "h1",
"text": "👋。",
"class": "font-light text-4xl"
},
{
"type": "br"
},
{
"type": "h1",
"text": "A NodeJS Full Stack ",
"class": "font-light text-4xl"
},
{
"type": "code",
"text": "<Developer />",
"class": "font-medium mx-2 text-3xl rounded p-1 bg-gray-200 dark:bg-gray-800/0 hover:dark:bg-gray-800/100 bg-opacity-0 hover:bg-opacity-100 transition-background duration-200"
},
{
"type": "span",
"class": "inline-block w-[1px] h-8 -bottom-2 relative bg-gray-800/80 dark:bg-gray-200/80 opacity-0 group-hover:opacity-100 transition-opacity duration-200 group-hover:animation-blink"
}
]
},
"description": "An independent developer coding with love."
},
"module": {
"activity": {
"enable": true,
"endpoint": "/fn/ps/update"
},
"donate": {
"enable": true,
"link": "https://afdian.net/@Innei",
"qrcode": [
"/static/images/20191211132347.png",
"/static/images/0424213144.png"
]
},
"bilibili": {
"liveId": 1434499
}
}
}
}
Click the button to save the configuration.
Conclusion
The process of deploying Yohaku this time was essentially a hands-on exercise in modern CI/CD workflows. From correcting the environment in the Baota panel to deep debugging of GitHub Actions, every Error resolved was a restructuring of my own knowledge system.
Successful deployment is only the first step; the real fun lies in subsequent creation or customization. If you encounter new "mysterious problems" while following this article, feel free to leave a comment to discuss, or directly consult the author Innei. I hope this article can provide key assistance when you build your own personal space.
