The Art of White Space: A Complete Guide to Deploying the Yohaku/Shiroi Theme with 1Panel
NoteThis is the second post in the "Mix Space + Yohaku Deployment Series," focusing on installing the Yohaku frontend theme. If you haven't deployed the backend yet, please start with the first post—Deploying the Mix Space Backend from Scratch with 1Panel
Yohaku, taken from the Japanese word for "white space," refers to the intentionally empty areas in a composition—often carrying more weight than the filled parts.
This is the latest-generation frontend theme in the Mix Space ecosystem. Its story began with the open-source Shiro, evolved through the closed-source sponsor version Shiroi, and has culminated in today's Yohaku—three generations of quiet iteration in both design language and implementation. The entire site uses writing as a metaphor; pages unfold like a letter, using restrained colors and breathing animations to make reading itself the protagonist.
A quick breakdown of the three generations:
- Shiro → The original open-source frontend theme for Mix Space, with code publicly available on GitHub.
- Shiroi → The closed-source donation version of Shiro, which evolved from the Shiro codebase and is accessible to sponsors.
- Yohaku (余白) → A completely new design that further evolved from Shiro / Shiroi. It is also maintained as a closed-source sponsor version and is the latest generation.
The focus of this deployment guide is Yohaku. Since it is a closed-source theme, you'll need to build the Docker image yourself. The steps below will walk you through the process. (This tutorial is also compatible with Shiroi.)
Prerequisites: This guide assumes you have access to the closed-source repository, have already deployed the Mix Space backend, and have these two addresses ready:
Backend API Address: e.g.,https://your-domain.com/api/v3(or/api/v2for backend versions before V13)Backend Gateway Address: e.g.,https://your-domain.com
If you haven't deployed the backend, please read the first post and complete that setup.
Step 1 · Building the Docker Image
(If you are using the open-source Shiro version, skip directly to Step 2.) Since the author does not provide pre-built Docker images for the Shiroi / Yohaku themes, we need to build them ourselves. However, do not build directly on a low-spec server—it will likely run out of memory and crash 💥
Our solution is to use GitHub Actions to build in the cloud, then push the image to a private GitHub Packages (ghcr.io) repository. Your server only needs to pull the ready-made image, which is very convenient.
1.1 Prepare the Repositories
Visit the following repository and click Fork in the top-right corner:
Then visit the following link to create a new repository (Choose visibility must be Private!!!). It is recommended to name it yohaku. If you choose a different name, you'll need to make corresponding changes in the workflow file later.
1.2 Apply for a GitHub Classic Personal Access Token
Actions needs permission to read the Shiroi private repository and push the built image to GitHub Packages. We need to prepare a Classic Token in advance.
Visit https://github.com/settings/tokens/new
Enter a note (e.g., yohaku-build), set the expiration to permanent, and select the following permissions:
| Permission | Purpose |
|---|---|
repo | Read and write repositories, including private repository access |
workflow | Update GitHub Action workflows |
write:packages | Push images to GitHub Packages |
read:packages | Pull images from GitHub Packages |
Click Generate token, and immediately copy and save the generated Token—it will only be displayed once!
1.3 Configure Repository Actions Secrets
Go to your forked repository and navigate to:
Settings → Secrets and variables → Actions → Repository secrets
Add the following two variables:
| Variable Name | Value |
|---|---|
BASE_URL | The domain bound to the Core backend (e.g., https://jiye.funcun.top) |
GH_PAT | The Personal Access Token you applied for in the previous step |
NEXT_PUBLIC_GATEWAY_URL | Optional, e.g., https://jiye.funcun.top |
NEXT_PUBLIC_API_URL | Optional, e.g., https://jiye.funcun.top/api/v3 |
Go to the newly created Yohaku repository and add the variable using the same steps:
| Variable Name | Value |
|---|---|
UPSTREAM_REPO_SECRET | The Personal Access Token you applied for in the previous step |
1.4 Enable and Trigger the Build
First, go to the newly created Yohaku repository and upload the upstream-sync.yml file from the forked repository. Then, run the following script on your server or another device, following the prompts (default options are recommended):
#!/bin/bash
set -euo pipefail
# =============================================
# Script Introduction:
# This script force-syncs a specified branch from an upstream repository
# to a target branch in your personal repository.
# Suitable for pulling updates from public/private upstream repos
# and pushing them to your own branch (e.g., for mirror syncing).
# Force-pushing will overwrite the target branch's history. Use with caution!
# =============================================
echo "======================================="
echo " Upstream Repo → Personal Repo Force Sync Tool"
echo "======================================="
echo ""
echo "This script will perform the following actions:"
echo "1. Clone the upstream repository to a temporary directory"
echo "2. Add your personal remote repository"
echo "3. Force-push the upstream branch to the target branch in your personal repo"
echo "Note: The existing content of the target branch will be completely overwritten!"
echo "======================================="
echo ""
# ---------- Collect your personal repository info ----------
read -r -p "Enter your GitHub username: " USERNAME
read -r -p "Enter your target repository name: " REPO
echo "Enter your GitHub Personal Access Token (input hidden, requires repo permission):"
read -r -s TOKEN
echo # newline
# ---------- Collect upstream repository info ----------
read -r -p "Is the upstream repository private? (y/n, default n): " UPSTREAM_PRIVATE
UPSTREAM_PRIVATE=${UPSTREAM_PRIVATE:-n}
read -r -p "Enter the full upstream repository URL (e.g., https://github.com/innei-dev/Yohaku.git): " UPSTREAM
if [ "$UPSTREAM_PRIVATE" = "y" ] || [ "$UPSTREAM_PRIVATE" = "Y" ]; then
echo "Upstream repo is private, please provide a token to access it (input hidden):"
read -r -s UPSTREAM_TOKEN
echo
# Construct authenticated upstream URL
UPSTREAM_AUTH_URL=$(echo "$UPSTREAM" | sed "s|https://|https://x-access-token:${UPSTREAM_TOKEN}@|")
else
UPSTREAM_AUTH_URL="$UPSTREAM"
fi
read -r -p "Enter a temporary directory name (default temp-upstream): " TEMP_DIR
TEMP_DIR=${TEMP_DIR:-temp-upstream}
read -r -p "Enter the upstream branch name (default main): " SRC_BRANCH
SRC_BRANCH=${SRC_BRANCH:-main}
read -r -p "Enter the target branch name to push to your personal repo (default sync): " DST_BRANCH
DST_BRANCH=${DST_BRANCH:-sync}
# ---------- Build personal remote URL ----------
MY_REMOTE="https://${USERNAME}:${TOKEN}@github.com/${USERNAME}/${REPO}.git"
# ---------- Handle temporary directory ----------
while [ -d "$TEMP_DIR" ] && [ "$(ls -A "$TEMP_DIR" 2>/dev/null)" ]; do
echo ""
echo "Warning: Directory '$TEMP_DIR' already exists and is not empty."
read -r -p "Delete and recreate? (y/n): " answer
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
rm -rf "$TEMP_DIR"
echo "Deleted old directory."
else
read -r -p "Enter a new temporary directory name: " TEMP_DIR
fi
done
# ---------- Execute sync ----------
echo ""
echo "Cloning upstream repository $UPSTREAM to $TEMP_DIR ..."
git clone "$UPSTREAM_AUTH_URL" "$TEMP_DIR"
cd "$TEMP_DIR"
echo "Adding your remote repository myrepo ..."
git remote add myrepo "$MY_REMOTE"
echo "Force-pushing $SRC_BRANCH -> myrepo/$DST_BRANCH ..."
git push --force myrepo "$SRC_BRANCH:$DST_BRANCH"
cd ..
echo "Cleaning up temporary directory $TEMP_DIR ..."
rm -rf "$TEMP_DIR"
echo ""
echo "======================================="
echo "Sync complete!"
echo "Force-pushed $SRC_BRANCH branch from $UPSTREAM"
echo "to $DST_BRANCH branch of $USERNAME/$REPO."
echo "======================================="
After completion, go to the Actions tab of the forked repository and click to enable it if prompted. Then find the build Workflow, click Run workflow on the right, and trigger it manually once.
The build process usually takes 5 to 10 minutes. Perfect time to grab a cup of tea ☕
Once the build is complete, you can see your image in the Packages section of the repository sidebar. The address format is:
ghcr.io/your-username(all-lowercase)/yohaku:latest
1.5 Configure the ghcr.io Private Registry in 1Panel
Since the image is stored in a private GitHub Container Registry, 1Panel needs to authenticate before it can pull it.
Log in to the 1Panel dashboard, go to Containers → Registries → Create Registry, and fill in:
| Field | Value |
|---|---|
| Name | ghcr.io (any easily identifiable name) |
| Registry Address | ghcr.io |
| Username | Your GitHub username |
| Password | Your Personal Access Token (GH_PAT) |
After saving, 1Panel will automatically verify the connection. Now pulling private images will be seamless 🔐
Step 2 · Installing Yohaku via 1Panel
2.1 Upload the Application Package
Log in to the 1Panel dashboard, go to Hosts → Files in the left menu, and navigate to the path:
/opt/1panel/resource/apps/local
Click upload and select the yohaku.zip file you downloaded from the forked repository.
2.2 Extract, and Mind the Path!
After the upload is complete, click on yohaku.zip and select Extract.
This is the most common pitfall for beginners, so please pay close attention!
When extracting, you must manually complete the target path as:
/opt/1panel/resource/apps/local/yohaku
An incorrect path may scatter files into the wrong directory, and the app store will fail to recognize this local application.
2.3 Sync Local Applications
Go to the 1Panel App Store, click the Sync Local Apps button in the top-right corner, wait a moment, and then type yohaku in the search box. You should see the application you just added.
Click Install to enter the configuration page.
2.4 Fill in the Installation Parameters
The installation page has four required fields (plus one read-only description). Fill them in from top to bottom:
🐳 Image Address
Enter the container image here. Choose based on the version you are deploying:
Open-source Shiro (use the official pre-built image directly):
innei/shiro:latest
Closed-source Shiroi / Yohaku (enter the private image you built in Step 1):
ghcr.io/your-username(all-lowercase)/shiroi:latest
📡 Public API URL (PUBLICAPIURL)
Enter your Mix Space backend API address:
https://your-backend-domain.com/api/v2
Make sure to keep the /api/v2 path at the end.
🌐 Public Gateway URL (PUBLICGATEWAYURL)
Enter your Mix Space backend gateway address (i.e., the backend root domain):
https://your-backend-domain.com
Do not add any path suffix.
🔗 API URL and Client API URL
The values for API_URL and NEXT_PUBLIC_CLIENT_API_URL should match the Public API URL. Just enter the same content:
https://your-backend-domain.com/api/v2
2.5 Start Installation 🎉
Confirm the configuration is correct and click Start Installation.
1Panel will automatically pull the image and start the container. This process may take a few minutes depending on your network. When the status shows Running, Yohaku is officially live!
Step 3 · Configuring Reverse Proxy and HTTPS
Refer to the previous blog post. If already configured, you can skip this step.
Appendix: Extended Markdown Syntax Supported by Yohaku
Inherited from the Shiro system, Yohaku supports a rich set of extended Markdown syntax, allowing your blog posts to go beyond plain text. Here are the special syntaxes you can use when writing—
Math Formulas (KaTeX)
Inline formula:
The mass-energy equivalence $E = mc^2$ changed humanity's understanding of the universe.
Block formula:
$$
\int_{-\infty}^{+\infty} e^{-x^2} dx = \sqrt{\pi}
$$
Notice / Banner
::: warning
This is a warning. The background will appear in a striking color.
:::
::: banner {note}
This is a note, suitable for adding extra explanations.
:::
::: banner {error}
This is an error alert, used to emphasize dangerous operations.
:::
GFM Alert Syntax
> [!NOTE]
> This is a note, reminding readers of certain matters.
> [!IMPORTANT]
> This is important information that should not be overlooked.
> [!WARNING]
> This is a warning, potentially involving risks.
Spoiler
The ending of this movie is ||the protagonist was actually dead all along||, which was unexpected.
Note: This is different from the strikethrough ~~text~~ effect. Spoiler hides the content with a mask, only revealing it on mouse hover.
Rich Link
For links on their own line, Yohaku will automatically render them as a card style with a cover image and summary:
https://github.com/Innei/Yohaku
Supported platforms include GitHub repositories, Commits, Issues, Gists, as well as YouTube, Twitter, etc.
Inline Link Icons
Inline links will automatically attach the corresponding website's Favicon:
Visit [Innei's homepage](https://innei.in) to learn more.
Mention
Thanks to [Innei]{GH@Innei} for creating such a beautiful theme.
GH@username will automatically render as a GitHub user card with an avatar.
Collapse
<details>
<summary>Click to expand for detailed content</summary>
Here is the detailed explanation hidden away...
</details>
FAQ
Q: What if the image pull speed is extremely slow or fails?
Pulling ghcr.io from servers in China can be slow. You can add a step in the GitHub Actions build Workflow to sync the push to Alibaba Cloud ACR, and then pull from Alibaba Cloud. For specific configuration, refer to Mint's Cabin tutorial.
Q: How do I troubleshoot if the Actions build fails?
Go to the repository's Actions page, click on the failed Workflow to view the detailed logs. Common causes:
- Insufficient
GH_PATpermissions; check ifrepoandwrite:packagesare selected. DOCKER_NAMESPACEis not all lowercase.- The repository was set to public, causing a permission conflict.
Q: Can't find yohaku in the app store after extraction?
Most likely the extraction path is wrong. Confirm the extraction target is /opt/1panel/resource/apps/local/yohaku (not /opt/1panel/resource/apps/local). After confirming, click "Sync Local Apps" again.
Q: The page is accessible, but it says backend connection failed?
Check the following:
- Are the four API address configuration items filled in correctly, especially ensuring the
/api/v2path is not omitted? - Does the Mix Space backend's
ALLOWED_ORIGINSinclude the Yohaku frontend domain? - Are the backend reverse proxy and HTTPS certificate working correctly?
Q: Getting the error error.api_fetchError Not found?
This is a bug in the source code. Publishing a "Note" post usually fixes it...
References
The following resources were consulted during the writing of this guide. Many thanks to the bloggers for their generous sharing 💝
- 1Panel Online Installation Documentation
- Building Shiroi Docker Image with GitHub Actions · Miku's Aurora Star
- Mix Space + Shiro Full Containerized Deployment Guide · Mint's Cabin
- Self-service 1Panel App Creation · FIT2CLOUD Community Forum
- Self-service 1Panel App Creation Tool
- Shiro Markdown Extended Syntax Documentation
- Yohaku Theme Documentation
- Adding NEXTPUBLICCLIENTAPIURL
- Adding API_URL
- Miscellaneous | Syncing Upstream Projects and Merging into Your Own Branch with Actions
- Building Yohaku's Docker Image with GitHub Actions
White space is an attitude.
May your blog become a letter worth unfolding slowly 🌿
