<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>豕豕豕 - Blog (EN)</title><description>Life is colorful!</description><link>https://blog.shishishi.cc/</link><language>en</language><image><url>https://blog.shishishi.cc/_astro/shishishi.BzPJMGkr.png</url><title>豕豕豕</title><link>https://blog.shishishi.cc/</link></image><atom:icon>https://blog.shishishi.cc/_astro/shishishi.BzPJMGkr.png</atom:icon><atom:link href="https://blog.shishishi.cc/en/rss.xml" rel="self" type="application/rss+xml"/><item><title>Play Tears of the Kingdom Natively on Mac: A MacNativeGames Build Guide</title><link>https://blog.shishishi.cc/en/blog/mac-native-games-totk-guide/</link><guid isPermaLink="true">https://blog.shishishi.cc/en/blog/mac-native-games-totk-guide/</guid><description>Build a Tears of the Kingdom Mac app with MacNativeGames: tools, updates, fonts, controls, graphics, save backups, and troubleshooting.</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you want to play &lt;em&gt;Tears of the Kingdom&lt;/em&gt; on a Mac, &lt;a href=&quot;https://github.com/ly05010419/MacNativeGames&quot;&gt;MacNativeGames&lt;/a&gt; offers a way to build a native app from your own game files. It adapts the game’s ARM64 code to run on Apple silicon, renders its graphics through Metal, and packages the result as an app you can open in Finder. Most of the setup happens during the first build.&lt;/p&gt;
&lt;p&gt;This guide is for Mac users who may be unfamiliar with build tools. It covers installing dependencies, building the base game or an updated version, and setting up controls, graphics, and save backups. It covers the Mac version only.&lt;/p&gt;
&lt;div class=&quot;note&quot; data-callout=&quot;note&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Version used in this guide&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;Prepared on October 22, 2026, this guide follows the documentation and build script at commit &lt;a href=&quot;https://github.com/ly05010419/MacNativeGames/tree/30a1b93d0776150fbaffd7c20f9a5ca0841c21c9&quot;&gt;&lt;code&gt;30a1b93&lt;/code&gt;&lt;/a&gt;. The commands select that source revision so the instructions can be checked against it. Requirements and controls may change in later revisions; consult their README when using newer code. The documentation and command arguments were checked for this article; a full game build and performance benchmarks were not repeated.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;1-check-the-requirements&quot;&gt;1. Check the requirements&lt;/h2&gt;
&lt;h3 id=&quot;hardware-and-storage&quot;&gt;Hardware and storage&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mac&lt;/td&gt;
&lt;td&gt;An Apple silicon model, such as M1, M2, M3, M4, or M5; this workflow does not cover Intel Macs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;macOS 26 or later, using the project’s Metal 4 backend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;The project recommends at least 16 GB; an 8 GB machine may use substantial swap during the build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;About 25 GB beyond the original game files for a base build, or about 40 GB for a fresh base-plus-update build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;Required to install dependencies and fetch pinned build sources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These storage estimates assume the work folder and finished app are on the same APFS volume, where cloning can share large data files. If cloning is unavailable or you put the app on another volume, allow roughly another 17 GB for the app copy. Make room before starting if your disk is nearly full.&lt;/p&gt;
&lt;h3 id=&quot;game-files&quot;&gt;Game files&lt;/h3&gt;
&lt;p&gt;Have these ready:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your own &lt;strong&gt;version 1.0.0 base game&lt;/strong&gt;, as an uncompressed XCI or NSP, with title ID &lt;code&gt;0100F2C0115B6000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Your own &lt;code&gt;prod.keys&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For an updated build, a &lt;strong&gt;separate cumulative update package&lt;/strong&gt; for 1.1.2, 1.2.1, or 1.4.3, in NSP or NSZ format.&lt;/li&gt;
&lt;li&gt;System shared fonts if you use an NSP base, as explained below.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;important&quot; data-callout=&quot;important&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Your files and the finished app&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;The project requires a game you own, with keys and system files from your own Switch. The repository supplies none of these, and this guide does not provide download sources. The app you build contains game content: keep it for personal use and do not share it.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The revision used here does not support compressed NSZ/XCZ base games, DLC, combined packages, or personalized tickets. NSZ support applies to updates. To build 1.4.3, supply the 1.0.0 base and the separate cumulative 1.4.3 update; intermediate updates are unnecessary.&lt;/p&gt;
&lt;h2 id=&quot;2-install-the-build-tools&quot;&gt;2. Install the build tools&lt;/h2&gt;
&lt;p&gt;Open Terminal and install the Xcode command line tools:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;xcode-select&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If macOS says they are already installed, continue. Otherwise, wait for the installer to finish.&lt;/p&gt;
&lt;p&gt;Install &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; using its official instructions, including the shell setup printed at the end. Its default Apple silicon prefix is &lt;code&gt;/opt/homebrew&lt;/code&gt;; see the &lt;a href=&quot;https://docs.brew.sh/Installation&quot;&gt;Homebrew installation documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check that Terminal can find it, then install the dependencies:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; opus&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; libvpx&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; python@3.14&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;python3.14&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final command should report Python 3.14.x. Use &lt;code&gt;python3.14&lt;/code&gt; throughout this guide to avoid selecting another Python installation.&lt;/p&gt;
&lt;p&gt;This revision’s Mac build does not require .NET, CMake, or the iOS toolchain. The script prepares its remaining environment, and the finished app does not need Python to run.&lt;/p&gt;
&lt;h3 id=&quot;optional-install-the-metal-toolchain&quot;&gt;Optional: install the Metal toolchain&lt;/h3&gt;
&lt;p&gt;If you have the full Xcode application, install the Metal toolchain following &lt;a href=&quot;https://developer.apple.com/documentation/Xcode/downloading-and-installing-additional-xcode-components&quot;&gt;Apple’s additional-component documentation&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;xcodebuild&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -downloadComponent&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; MetalToolchain&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The project uses this to compile startup shaders into the app. If you only have the command line tools, you can skip this step. The build can still proceed using the Mac’s Metal cache for the app’s location, which matters if you move the app later.&lt;/p&gt;
&lt;h2 id=&quot;3-download-the-source-and-organize-your-files&quot;&gt;3. Download the source and organize your files&lt;/h2&gt;
&lt;p&gt;The following puts the source under &lt;code&gt;~/Projects&lt;/code&gt;. If you already downloaded it elsewhere, enter that directory instead.&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Projects&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Projects&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://github.com/ly05010419/MacNativeGames.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; MacNativeGames&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; checkout&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; 30a1b93d0776150fbaffd7c20f9a5ca0841c21c9&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final command selects the revision used in this article. Git may display a &lt;code&gt;detached HEAD&lt;/code&gt; notice; that is normal when selecting a specific commit and does not prevent building.&lt;/p&gt;
&lt;p&gt;The examples below assume this layout. &lt;strong&gt;Replace the sample filenames and paths with your own.&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/Games/TOTK/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  base.xci            Base game; base.nsp for the NSP route&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  update-1.4.3.nsp     Optional separate update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  firmware/           Needed for the NSP route; contains unpacked .nca files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/keys/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  prod.keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a personal Applications folder for the finished app:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Applications&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;~/Applications&lt;/code&gt; is inside your home directory and differs from the system-wide &lt;code&gt;/Applications&lt;/code&gt;. It normally needs no administrator privileges. Building directly to the intended location also reduces the need to rebuild location-dependent shader caches later.&lt;/p&gt;
&lt;div class=&quot;tip&quot; data-callout=&quot;tip&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Paths containing spaces&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;Quote the complete path, for example &lt;code&gt;&quot;$HOME/Games/TOTK/My Game.xci&quot;&lt;/code&gt;. You can also drag a file from Finder into Terminal to insert its path. A tilde inside double quotes does not expand, so these examples use &lt;code&gt;&quot;$HOME/...&quot;&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;4-choose-your-build-command&quot;&gt;4. Choose your build command&lt;/h2&gt;
&lt;p&gt;Run the following from the &lt;code&gt;MacNativeGames&lt;/code&gt; source directory. &lt;strong&gt;Choose the command that matches your files; you do not need to run every example.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;xci-base-build-version-100&quot;&gt;XCI base: build version 1.0.0&lt;/h3&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;python3.14&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; build_app.py&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Games/TOTK/base.xci&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -k&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/keys/prod.keys&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -o&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Applications/Tears of the Kingdom.app&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This builds 1.0.0. An XCI usually supplies shared fonts through its system update partition. If that partition is missing, add &lt;code&gt;--firmware&lt;/code&gt; or &lt;code&gt;--shared-fonts&lt;/code&gt; as described next.&lt;/p&gt;
&lt;h3 id=&quot;nsp-base-supply-the-shared-fonts&quot;&gt;NSP base: supply the shared fonts&lt;/h3&gt;
&lt;p&gt;An NSP base does not include system shared fonts, so provide an unpacked firmware directory:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;python3.14&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; build_app.py&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Games/TOTK/base.nsp&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -k&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/keys/prod.keys&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  --firmware&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Games/TOTK/firmware&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -o&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Applications/Tears of the Kingdom.app&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The firmware path must point to a directory containing &lt;code&gt;.nca&lt;/code&gt; files, or an installed firmware’s &lt;code&gt;registered&lt;/code&gt; directory. Do not pass a firmware ZIP archive directly.&lt;/p&gt;
&lt;p&gt;If you previously extracted the fonts, replace &lt;code&gt;--firmware&lt;/code&gt; and its path with:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;--shared-fonts&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Games/TOTK/TOTK_build/totk/extracted/firmware/shared-fonts&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is an argument fragment to insert into the build command. The directory must contain the six files from &lt;code&gt;0-standard.bfttf&lt;/code&gt; through &lt;code&gt;5-nintendo-extension.bfttf&lt;/code&gt;. The builder validates them and can reuse valid fonts already in the work folder. Ordinary macOS fonts are not substitutes.&lt;/p&gt;
&lt;h3 id=&quot;base-plus-update-build-version-143&quot;&gt;Base plus update: build version 1.4.3&lt;/h3&gt;
&lt;p&gt;For an XCI base and a separate update:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;python3.14&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; build_app.py&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Games/TOTK/base.xci&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -k&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/keys/prod.keys&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  --update&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Games/TOTK/update-1.4.3.nsp&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -o&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Applications/Tears of the Kingdom.app&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For an NSP base, keep the font argument:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;python3.14&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; build_app.py&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Games/TOTK/base.nsp&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -k&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/keys/prod.keys&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  --firmware&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Games/TOTK/firmware&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  --update&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Games/TOTK/update-1.4.3.nsp&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -o&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Applications/Tears of the Kingdom.app&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your update is an NSZ, change the &lt;code&gt;--update&lt;/code&gt; path to that &lt;code&gt;.nsz&lt;/code&gt; file; the builder handles decompression and verification. For 1.1.2 or 1.2.1, supply that version’s cumulative update instead. &lt;strong&gt;Omitting &lt;code&gt;--update&lt;/code&gt; builds 1.0.0; updates are not selected automatically.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Argument&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File path after &lt;code&gt;build_app.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The 1.0.0 base game&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your keys file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--update&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional separate cumulative update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--firmware&lt;/code&gt; / &lt;code&gt;--shared-fonts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alternative font sources; use one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-o&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full output path, ending in &lt;code&gt;.app&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--work&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional custom intermediate-file directory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Without &lt;code&gt;-o&lt;/code&gt;, the app is written next to the base game. With the file layout above, the default work folder is &lt;code&gt;~/Games/TOTK/TOTK_build&lt;/code&gt;; it holds extracted data and build logs. After fixing a recoverable error, rerun the same command to reuse completed steps before considering removal of the whole work folder.&lt;/p&gt;
&lt;h3 id=&quot;how-long-does-it-take&quot;&gt;How long does it take?&lt;/h3&gt;
&lt;p&gt;The script extracts and verifies game data, rewrites code modules, prepares shaders, and packages and signs the app. Let it continue while progress is being reported. Memory, storage, network speed, and shader compilation all affect the first build.&lt;/p&gt;
&lt;p&gt;The project reports that GPU shader compilation alone can take around 25 minutes on an 8 GB M1 MacBook Air. This helps explain a long wait; it is not an estimate for your complete build.&lt;/p&gt;
&lt;p&gt;At the end, the script runs the launcher in &lt;code&gt;--check&lt;/code&gt; mode. That completes executable checks and related startup checks. &lt;strong&gt;You still need to open the game to verify graphics, sound, and controls.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;5-let-the-first-launch-finish-preparing&quot;&gt;5. Let the first launch finish preparing&lt;/h2&gt;
&lt;p&gt;In Finder, press &lt;code&gt;Command + Shift + G&lt;/code&gt;, enter &lt;code&gt;~/Applications&lt;/code&gt;, and open &lt;code&gt;Tears of the Kingdom.app&lt;/code&gt;. Alternatively:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Applications/Tears of the Kingdom.app&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first launch verifies game data and prepares shaders. The project observed roughly 20 seconds for data verification locally; your wait depends on the machine and cache state. Allow visible progress to finish.&lt;/p&gt;
&lt;p&gt;Shader warm-up can continue behind the title screen, with another wait when first entering the world. The builder already generates startup shaders from your game data and the shared shader list, so a cache downloaded from another player is normally unnecessary.&lt;/p&gt;
&lt;p&gt;Unvisited scenes may still need new shader variants and briefly stutter. First-use compilation and persistently low frame rates call for different checks. If a second visit is noticeably smoother, the cache is helping. If it stays slow, investigate graphics settings, memory pressure, and the particular scene.&lt;/p&gt;
&lt;h3 id=&quot;can-you-move-the-app&quot;&gt;Can you move the app?&lt;/h3&gt;
&lt;p&gt;Yes, though choosing its destination before building is useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;With the Metal toolchain:&lt;/strong&gt; the app carries its precompiled startup shader libraries and can use those after moving.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Without the Metal toolchain:&lt;/strong&gt; the Mac’s compiled shader cache is associated with the app’s location. Moving the app may trigger recompilation, as can a system cache cleanup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why the examples use &lt;code&gt;-o&lt;/code&gt; to build directly to the final directory. Copying across volumes can also require another copy of the game data and another macOS app check.&lt;/p&gt;
&lt;h2 id=&quot;6-controller-and-keyboard-controls&quot;&gt;6. Controller and keyboard controls&lt;/h2&gt;
&lt;h3 id=&quot;controllers-watch-the-confirm-buttons-position&quot;&gt;Controllers: watch the confirm button’s position&lt;/h3&gt;
&lt;p&gt;Compatible controllers recognized by macOS, such as DualSense and Xbox controllers, are mapped to a Switch Pro Controller. The default face-button mapping follows position:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Physical position&lt;/th&gt;
&lt;th&gt;Switch button&lt;/th&gt;
&lt;th&gt;Xbox&lt;/th&gt;
&lt;th&gt;PlayStation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Right&lt;/td&gt;
&lt;td&gt;A: confirm / interact&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;Circle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;B: cancel / sprint&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Cross&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;X: jump&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Triangle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Left&lt;/td&gt;
&lt;td&gt;Y: attack&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;Square&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;An on-screen A prompt therefore corresponds to the right-hand B button on an Xbox controller by default. Motion and rumble depend on the controller and what macOS exposes.&lt;/p&gt;
&lt;p&gt;Custom controller mappings go in:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/Library/Application Support/MacNativeGames/TOTK/controls.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, assign a DualSense touchpad click to the map button:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;touchpad&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; minus&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unspecified buttons keep their defaults. Restart the game after editing. If the launcher reports an invalid line, check that line; deleting the configuration file restores the defaults.&lt;/p&gt;
&lt;h3 id=&quot;keyboard-layout-for-this-revision&quot;&gt;Keyboard layout for this revision&lt;/h3&gt;
&lt;p&gt;These are the defaults at the commit used in this guide. Keys refer to physical US keyboard positions. For other revisions, check their README and in-game shortcut display.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mac key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;W, A, S, D&lt;/td&gt;
&lt;td&gt;Move: left stick&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arrow keys&lt;/td&gt;
&lt;td&gt;Camera: right stick&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;J&lt;/td&gt;
&lt;td&gt;A: confirm / interact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;K or Space&lt;/td&gt;
&lt;td&gt;B: cancel / sprint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;X: jump&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;U&lt;/td&gt;
&lt;td&gt;Y: attack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q / E&lt;/td&gt;
&lt;td&gt;L / R&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Option / Shift&lt;/td&gt;
&lt;td&gt;ZL / ZR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F, T, H, G&lt;/td&gt;
&lt;td&gt;D-pad left, up, right, down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Z / C&lt;/td&gt;
&lt;td&gt;Left / right stick click&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return&lt;/td&gt;
&lt;td&gt;Menu (+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tab or Esc&lt;/td&gt;
&lt;td&gt;Map (−)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;7-adjust-graphics-one-setting-at-a-time&quot;&gt;7. Adjust graphics one setting at a time&lt;/h2&gt;
&lt;p&gt;First enter the game with its default settings and check basic controls. Then try the number keys one at a time so you can identify the effect of each change.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Cycle full overlay, FPS only, and hidden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Cycle interpolation: off, 30+30, half refresh rate, unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Cycle MetalFX 2K, native 2K, and off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Cycle &lt;code&gt;16:9&lt;/code&gt;, fit to window/display, and &lt;code&gt;21:9&lt;/code&gt;; versions 1.2.1 / 1.4.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Toggle MetalFX temporal anti-aliasing; version 1.4.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two details help interpret the results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interpolated frame counts include generated frames and do not directly represent the game’s logic frame rate.&lt;/li&gt;
&lt;li&gt;Native 2K changes the composition canvas and UI. The 3D scene has separate render scaling, so this does not force every internal render target to 1440p.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The game follows your Mac’s language. To temporarily select Simplified Chinese, fully quit the game and launch it with:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --env&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; TOTK_LANG=zh-Hans&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/Applications/Tears of the Kingdom.app&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That environment variable applies to this launch. Running &lt;code&gt;open&lt;/code&gt; again while the app is already running does not reload its startup environment.&lt;/p&gt;
&lt;h2 id=&quot;8-find-and-back-up-your-saves&quot;&gt;8. Find and back up your saves&lt;/h2&gt;
&lt;p&gt;The app, build work folder, and personal saves are stored separately:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App in this guide&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/Applications/Tears of the Kingdom.app&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default work folder in this guide&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/Games/TOTK/TOTK_build&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Save profile&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/Library/Application Support/MacNativeGames/TOTK/profile&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shader cache&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/Library/Application Support/MacNativeGames/TOTK/shader-cache&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current run’s log&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/Library/Logs/MacNativeGames/totk.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Previous run’s log&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/Library/Logs/MacNativeGames/totk.previous.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Use &lt;code&gt;Command + Shift + G&lt;/code&gt; in Finder to open these locations.&lt;/p&gt;
&lt;p&gt;Before changing game versions, &lt;strong&gt;quit the game and copy the entire &lt;code&gt;profile&lt;/code&gt; folder somewhere safe&lt;/strong&gt;. Before an updated version starts, the project also creates a backup beside the profile, such as &lt;code&gt;profile-before-1.4.3&lt;/code&gt;; startup stops if that backup fails. Keep your pre-upgrade backup for a rollback: a newer save may not work with an older game version.&lt;/p&gt;
&lt;p&gt;To update the project’s code, obtain the desired newer source and rebuild using the original base game, the same update package, and the same output path. Quit the running game first, and preserve necessary arguments such as &lt;code&gt;--firmware&lt;/code&gt;, &lt;code&gt;--shared-fonts&lt;/code&gt;, and &lt;code&gt;--work&lt;/code&gt;. Building to the same path replaces the app; saves remain in the separate user directory above.&lt;/p&gt;
&lt;h2 id=&quot;9-troubleshooting&quot;&gt;9. Troubleshooting&lt;/h2&gt;
&lt;h3 id=&quot;terminal-cannot-find-brew-or-python314&quot;&gt;Terminal cannot find &lt;code&gt;brew&lt;/code&gt; or &lt;code&gt;python3.14&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Complete the shell setup printed by the Homebrew installer, reopen Terminal, and repeat the version checks in section 2. An existing &lt;code&gt;python3&lt;/code&gt; command may point to another version. Use the Apple silicon Homebrew installation to avoid mixing dependencies from an Intel environment.&lt;/p&gt;
&lt;h3 id=&quot;shared-fonts-are-missing&quot;&gt;Shared fonts are missing&lt;/h3&gt;
&lt;p&gt;Check the base format. NSP requires &lt;code&gt;--firmware&lt;/code&gt; or &lt;code&gt;--shared-fonts&lt;/code&gt;; an XCI missing its system update partition needs them too. The firmware must be unpacked, and an extracted font directory must contain the six &lt;code&gt;.bfttf&lt;/code&gt; files.&lt;/p&gt;
&lt;h3 id=&quot;a-version-or-hash-check-fails&quot;&gt;A version or hash check fails&lt;/h3&gt;
&lt;p&gt;Check that the base is 1.0.0, the update is a supported separate cumulative package, the files are intact, and your keys match your content. Validation detects unsupported or damaged inputs. Do not edit the expected hashes to bypass the failure.&lt;/p&gt;
&lt;h3 id=&quot;no-game-window-appears-after-opening-the-app&quot;&gt;No game window appears after opening the app&lt;/h3&gt;
&lt;p&gt;First launch, moving the app, or copying it across volumes can trigger system checks or data verification. Look for progress and inspect the log. If an explicit error appears, address it. A successful build-time &lt;code&gt;--check&lt;/code&gt; does not establish that gameplay works.&lt;/p&gt;
&lt;h3 id=&quot;stutter-persists-or-memory-pressure-is-high&quot;&gt;Stutter persists or memory pressure is high&lt;/h3&gt;
&lt;p&gt;Close other memory-heavy applications and inspect memory pressure and swap in Activity Monitor. Use key 3 to disable additional 2K modes for comparison, wait for background warm-up, and revisit the same scene. Avoid judging final performance during first-use compilation. Clearing every cache is not a routine optimization: the shaders would need to be prepared again.&lt;/p&gt;
&lt;h3 id=&quot;effects-play-but-dialogue-or-music-is-missing&quot;&gt;Effects play, but dialogue or music is missing&lt;/h3&gt;
&lt;p&gt;Check the selected macOS audio output and volume, then look for CoreAudio or decoder errors in the log. The project includes a streamed-audio path fix; see its &lt;a href=&quot;https://github.com/ly05010419/MacNativeGames/blob/30a1b93d0776150fbaffd7c20f9a5ca0841c21c9/docs/audio-troubleshooting.md&quot;&gt;audio troubleshooting guide&lt;/a&gt;. If using an early app, rebuild from source containing the fix with your original game files. Compilation stalls can also interrupt playback and need separate diagnosis.&lt;/p&gt;
&lt;h3 id=&quot;you-want-to-install-mods&quot;&gt;You want to install mods&lt;/h3&gt;
&lt;p&gt;Complete a build and launch without mods first. Then follow the project’s &lt;a href=&quot;https://github.com/ly05010419/MacNativeGames/blob/30a1b93d0776150fbaffd7c20f9a5ca0841c21c9/docs/mods.md&quot;&gt;mod documentation&lt;/a&gt;. Code patches are merged at build time with &lt;code&gt;--mod&lt;/code&gt;; RomFS data has separate installation and manifest requirements. Do not drop another environment’s mod folder into the app bundle.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ly05010419/MacNativeGames/blob/30a1b93d0776150fbaffd7c20f9a5ca0841c21c9/README.md&quot;&gt;MacNativeGames README at the revision used here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ly05010419/MacNativeGames/blob/30a1b93d0776150fbaffd7c20f9a5ca0841c21c9/build_app.py&quot;&gt;Build script and argument definitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ly05010419/MacNativeGames/blob/30a1b93d0776150fbaffd7c20f9a5ca0841c21c9/docs/first-launch-shaders.md&quot;&gt;First-launch shader preparation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.brew.sh/Installation&quot;&gt;Homebrew installation documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/Xcode/downloading-and-installing-additional-xcode-components&quot;&gt;Apple: downloading and installing additional Xcode components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Deploy Xboard Directly on Linux for a Self-Hosted Proxy Panel</title><link>https://blog.shishishi.cc/en/blog/xboard-linux-deployment-guide/</link><guid isPermaLink="true">https://blog.shishishi.cc/en/blog/xboard-linux-deployment-guide/</guid><description>A full deployment walkthrough covering Xboard, Caddy HTTPS, Hysteria2 nodes, subscription domains, SMTP mail, and the final production checklist.</description><pubDate>Mon, 22 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This guide comes from a recent end-to-end deployment session where I set up an Xboard panel, Caddy for HTTPS, Hysteria2 nodes, a dedicated subscription domain, SMTP mail, and the final production hardening steps from scratch.&lt;/p&gt;
&lt;p&gt;This setup fits a fairly common pattern: one public Linux server runs the Xboard panel, and one or more Linux VPS instances serve as nodes. You can host Xboard locally if you really want to, but you still need at least one overseas server for the node side.&lt;/p&gt;
&lt;h2 id=&quot;preparation-checklist&quot;&gt;Preparation Checklist&lt;/h2&gt;
&lt;p&gt;Before you start, make sure you already have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One panel server: Ubuntu 22.04 / 24.04 or Debian 12, ideally with at least 1 GB RAM, with &lt;code&gt;80/tcp&lt;/code&gt; and &lt;code&gt;443/tcp&lt;/code&gt; available.&lt;/li&gt;
&lt;li&gt;One or more node servers: Ubuntu 22.04 / 24.04 or Debian 12, running Hysteria2, with &lt;code&gt;8443/udp&lt;/code&gt; open, or later &lt;code&gt;20000-29999/udp&lt;/code&gt; if you plan to use port hopping.&lt;/li&gt;
&lt;li&gt;A split domain layout: one panel domain, one subscription domain, and one or more node domains.&lt;/li&gt;
&lt;li&gt;Access to Cloudflare DNS management, since the examples in this post assume Cloudflare.&lt;/li&gt;
&lt;li&gt;An SMTP provider for sending mail. This guide uses Brevo’s free SMTP.&lt;/li&gt;
&lt;li&gt;A mailbox that can receive verification messages. Here it is paired with Cloudflare Email Routing.&lt;/li&gt;
&lt;li&gt;Basic system tools: &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;wget&lt;/code&gt;, &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;ufw&lt;/code&gt;, and later Docker, Caddy, and certbot.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The example domains in this guide are:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Panel domain: v.shishishi3.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Subscription domain: 474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Hong Kong node: hk.474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;US node: us.474769754.xyz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace them with your own domains in practice.&lt;/p&gt;
&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;
&lt;p&gt;This post uses a direct Linux deployment model:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;User -&amp;gt; HTTPS domain -&amp;gt; Linux server with Caddy -&amp;gt; Xboard container&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;User -&amp;gt; HY2 node domain over UDP -&amp;gt; Linux node server running xboard-node&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I strongly recommend separating the responsibilities of your domains:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;v.shishishi3.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Purpose: the main Xboard panel, admin login, and management operations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Purpose: subscription distribution entry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;hk.474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Purpose: Hong Kong node domain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;us.474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Purpose: US node domain&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;note&quot; data-callout=&quot;note&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;Do not use the same domain for the admin panel, subscriptions, and nodes. Splitting them makes troubleshooting and risk control much cleaner, and it also reduces the blast radius if one part gets blocked or abused.&lt;/p&gt;&lt;p&gt;If you want a separate subscription entry, a cheap numeric domain is usually enough. In practice it often costs less than &lt;code&gt;¥10&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;tutorial&quot;&gt;Tutorial&lt;/h2&gt;
&lt;h3 id=&quot;1-prepare-the-servers&quot;&gt;1. Prepare the Servers&lt;/h3&gt;
&lt;p&gt;Recommended specs for the panel server:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;OS: Ubuntu 22.04 / 24.04 or Debian 12&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Memory: 1 GB minimum, 2 GB preferred&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Ports: 80/tcp, 443/tcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recommended specs for node servers:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;OS: Ubuntu 22.04 / 24.04 or Debian 12&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Protocol: Hysteria2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Ports: 8443/udp, or 20000-29999/udp for port hopping&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start with a system update:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -y&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; curl&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; wget&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ca-certificates&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ufw&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;tip&quot; data-callout=&quot;tip&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Troubleshooting&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;If this gets stuck because of an old Ookla speedtest repository, jump to &lt;a href=&quot;#pitfall-apt-source&quot;&gt;Broken apt source&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;2-configure-dns&quot;&gt;2. Configure DNS&lt;/h3&gt;
&lt;p&gt;Example Cloudflare DNS records:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A  v    panel server IP  DNS only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A  @    panel server IP  DNS only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A  hk   Hong Kong node IP  DNS only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;A  us   US node IP  DNS only&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Important details:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For the root domain &lt;code&gt;474769754.xyz&lt;/code&gt;, the hostname should be &lt;code&gt;@&lt;/code&gt;, not the literal domain string.&lt;/li&gt;
&lt;li&gt;Node domains must stay gray-clouded, meaning &lt;code&gt;DNS only&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;HY2 uses UDP, and Cloudflare’s regular proxy does not forward that traffic.&lt;/li&gt;
&lt;li&gt;Fresh DNS changes may take a few minutes to propagate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Verify with:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;nslookup&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; 474769754.xyz&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 1.1.1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;nslookup&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; hk.474769754.xyz&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 1.1.1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;nslookup&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; us.474769754.xyz&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 1.1.1.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;tip&quot; data-callout=&quot;tip&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Troubleshooting&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;If your local machine is using a proxy or TUN and you see &lt;code&gt;198.18.x.x&lt;/code&gt; fake IP results, jump to &lt;a href=&quot;#pitfall-fake-ip&quot;&gt;Fake-IP interference&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;3-install-docker-and-compose&quot;&gt;3. Install Docker and Compose&lt;/h3&gt;
&lt;p&gt;If Docker is not installed yet:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -fsSL&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://get.docker.com&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --now&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; docker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-deploy-xboard&quot;&gt;4. Deploy Xboard&lt;/h3&gt;
&lt;p&gt;The official &lt;code&gt;compose&lt;/code&gt; branch of Xboard ships with a Docker Compose template that exposes container port &lt;code&gt;7001&lt;/code&gt; to host port &lt;code&gt;7001&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /opt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -b&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --depth&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://github.com/cedar2025/Xboard&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; Xboard&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /opt/Xboard&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure &lt;code&gt;compose.yaml&lt;/code&gt; contains something like:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  xboard&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;ghcr.io/cedar2025/xboard:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;    restart&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;unless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;7001:7001&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;./.env:/www/.env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;./.docker/.data/:/www/.docker/.data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;./storage/logs:/www/storage/logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;./storage/theme:/www/storage/theme&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;./plugins:/www/plugins&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;redis-data:/data&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a simple setup, initialize it with SQLite and the built-in Redis:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; docker&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --rm&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -e&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ENABLE_SQLITE=&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -e&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ENABLE_REDIS=&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -e&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ADMIN_ACCOUNT=admin@example.com&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#98C379&quot;&gt;  xboard&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; php&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; artisan&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; xboard:install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The installer will print:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Admin email&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Admin password&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Admin path&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save those values, then bring the service up:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; docker&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; up&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -d&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; docker&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; docker&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; logs&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -f&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test local access:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -I&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; http://127.0.0.1:7001&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The expected result is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-install-caddy&quot;&gt;5. Install Caddy&lt;/h3&gt;
&lt;p&gt;Install Caddy:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -y&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; debian-keyring&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; debian-archive-keyring&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-transport-https&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; curl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -1sLf&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &apos;https://dl.cloudsmith.io/public/caddy/stable/gpg.key&apos;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;  | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; gpg&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --dearmor&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /usr/share/keyrings/caddy-stable-archive-keyring.gpg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -1sLf&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &apos;https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt&apos;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;  | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; tee&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/apt/sources.list.d/caddy-stable.list&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -y&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Write this into &lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;v.shishishi3.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	reverse_proxy 127.0.0.1:7001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;474769754.xyz {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	handle /s/* {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		reverse_proxy 127.0.0.1:7001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	handle /api/v1/client/subscribe* {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		reverse_proxy 127.0.0.1:7001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	respond &quot;Not Found&quot; 404&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this means:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;v.shishishi3.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reverse-proxy the full Xboard panel.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Expose only subscription paths and keep the admin panel hidden.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;/s/*&lt;/code&gt; rule is important. A real Xboard subscription link often looks like:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://474769754.xyz/s/xxxxxxxxxxxxxxxx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;tip&quot; data-callout=&quot;tip&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Troubleshooting&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;If you only allow &lt;code&gt;/api/v1/client/subscribe*&lt;/code&gt;, the client import request can still get blocked by Caddy. If that happens, jump to &lt;a href=&quot;#pitfall-subscription-404&quot;&gt;Subscription 404&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Validate and reload Caddy:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; validate&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --config&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/caddy/Caddyfile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; reload&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; journalctl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -f&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test both domains:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -I&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://v.shishishi3.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -I&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://474769754.xyz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected results:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://v.shishishi3.com -&amp;gt; 200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://474769754.xyz -&amp;gt; 404&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;404&lt;/code&gt; on the root of the subscription domain is normal, because we intentionally do not expose the panel there.&lt;/p&gt;
&lt;div class=&quot;tip&quot; data-callout=&quot;tip&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Troubleshooting&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;If you get &lt;code&gt;unexpected eof while reading&lt;/code&gt; here, jump to &lt;a href=&quot;#pitfall-tls-eof&quot;&gt;Caddy TLS EOF&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;6-basic-xboard-configuration&quot;&gt;6. Basic Xboard Configuration&lt;/h3&gt;
&lt;p&gt;After logging into the panel, start by changing:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Site name: your own name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Site URL: your panel domain or subdomain, such as https://v.shishishi3.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Subscription URL: your subscription domain or subdomain, such as https://474769754.xyz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recommended security settings:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Admin path: change it to a random path&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Open registration: enable only when ready, or keep it closed for private use&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Email verification: enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Login captcha: enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Registration captcha: enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Subscription anti-leech: enabled&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the panel only offers one URL field, use:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://v.shishishi3.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it provides a separate subscription URL field, use:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://474769754.xyz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;7-smtp-mail-configuration&quot;&gt;7. SMTP Mail Configuration&lt;/h3&gt;
&lt;p&gt;If the panel is for personal use only, you may not need this section. But if you want email verification or password reset, set up SMTP properly.&lt;/p&gt;
&lt;p&gt;Cloudflare Email Routing can receive and forward mail, but it is not an SMTP sending server.&lt;/p&gt;
&lt;p&gt;This guide uses Brevo’s free SMTP:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SMTP host: smtp-relay.brevo.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SMTP port: 587&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Encryption: TLS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SMTP username: the SMTP Login provided by Brevo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SMTP password: your Brevo SMTP key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;From address: noreply@474769754.xyz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few details matter:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Port &lt;code&gt;587&lt;/code&gt; means &lt;code&gt;TLS&lt;/code&gt; or &lt;code&gt;STARTTLS&lt;/code&gt;, not &lt;code&gt;SSL/TLS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Addresses like &lt;code&gt;ac96b9001@smtp-brevo.com&lt;/code&gt; are SMTP login usernames, not the visible sender address.&lt;/li&gt;
&lt;li&gt;The sender address can be &lt;code&gt;noreply@474769754.xyz&lt;/code&gt;, but it must be verified in Brevo.&lt;/li&gt;
&lt;li&gt;Cloudflare Email Routing can receive Brevo’s verification message for &lt;code&gt;noreply@474769754.xyz&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Cloudflare DNS allows only one SPF record. If Cloudflare already gives you:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;v=spf1 include:_spf.mx.cloudflare.net ~all&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and Brevo asks for:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;v=spf1 include:spf.brevo.com ~all&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;merge them into:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;v=spf1 include:_spf.mx.cloudflare.net include:spf.brevo.com ~all&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If verification mail is not arriving, inspect the Xboard logs:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /opt/Xboard&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; docker&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; logs&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --tail=160&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; xboard&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;SMTP\|Brevo\|SendEmailJob\|Unauthorized&quot;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; storage/logs/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;tip&quot; data-callout=&quot;tip&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Troubleshooting&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;If you see &lt;code&gt;525 5.7.1 Unauthorized IP address&lt;/code&gt; in the logs, jump to &lt;a href=&quot;#pitfall-smtp-unauthorized&quot;&gt;SMTP unauthorized IP&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;8-plan-design&quot;&gt;8. Plan Design&lt;/h3&gt;
&lt;p&gt;Adjust your plans to fit your actual node machines and bandwidth. The examples below are simply the way I structured mine.&lt;/p&gt;
&lt;p&gt;If the US node has plenty of traffic while the Hong Kong node is relatively limited, a reasonable split is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;US San Jose: main node, 1x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Hong Kong: backup node, 5x / 8x / 10x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example low-cost plan:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Plan name: Vela Basic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Monthly: ¥3.9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Yearly: ¥36&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Traffic: 50 GB / month&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Speed cap: 300 Mbps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Devices: 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;US: 1x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HK: 5x or higher&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the pricing is closer to:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Monthly: ¥3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Yearly: ¥30&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then a more conservative package may be:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Traffic: 40 GB / month&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Speed cap: 200 Mbps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Devices: 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HK: 8x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example plan description template:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;## Plan Details&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; Traffic: {{transfer}} GB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; Speed limit: {{speed}} Mbps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; Concurrent devices: {{devices}}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; Primary node: San Jose, US West Coast&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; Backup node: high-multiplier Hong Kong node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;## Service Notes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; Traffic resets {{reset_method}}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; Supports common iOS, Android, Windows, and macOS clients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; The San Jose node is the primary route, with a 10 Gbps uplink and a real-world peak around 1.8 Gbps to 6 Gbps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;4.&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; The Hong Kong node is a backup route and uses a higher multiplier because monthly traffic is limited&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;5.&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; Best suited for browsing, social media, AI tools, and light streaming&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;6.&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; BT/PT, mining, scanning, spam, and long-term bandwidth abuse are not allowed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E5C07B&quot;&gt;7.&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; 24/7 technical support&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;9-create-hy2-nodes&quot;&gt;9. Create HY2 Nodes&lt;/h3&gt;
&lt;p&gt;In the Xboard backend, choose:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Hysteria&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;figure style=&quot;text-align: center;&quot; data-lightbox=&quot;article&quot;&gt;&lt;img alt=&quot;The basic Hysteria node creation form in Xboard&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1160&quot; height=&quot;1508&quot; src=&quot;https://blog.shishishi.cc/_astro/node-config-basic.DprqpAJX_Z1wMurh.webp&quot; srcset=&quot;&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Example Hong Kong node:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Node name: HK-HY2-01&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Base multiplier: 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Traffic limit: 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Tags: HK, HY2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Permission group: Default&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Node address: hk.474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Connect port: 8443&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Service port: 8443&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Protocol version: V2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Obfuscation: enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Obfuscation implementation: Salamander&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Obfuscation password: random&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SNI: hk.474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Allow insecure: disabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ECH: disabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Upload bandwidth: set to the real line capacity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Download bandwidth: set to the real line capacity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Bind server: independent deployment or bind to the matching server&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example US node:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Node name: US-HY2-01&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Base multiplier: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Node address: us.474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Connect port: 8443&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Service port: 8443&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SNI: us.474769754.xyz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Things to keep straight:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ECH is not your TLS certificate.&lt;/li&gt;
&lt;li&gt;HY2 does not sit behind Caddy.&lt;/li&gt;
&lt;li&gt;Caddy cannot &lt;code&gt;reverse_proxy&lt;/code&gt; ordinary HY2 UDP traffic.&lt;/li&gt;
&lt;li&gt;Node domains must stay gray-clouded.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This next screenshot corresponds to the transport-layer settings, including connect port, service port, obfuscation, SNI, and ECH:&lt;/p&gt;
&lt;figure style=&quot;text-align: center;&quot; data-lightbox=&quot;article&quot;&gt;&lt;img alt=&quot;The Hysteria transport configuration in Xboard&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1158&quot; height=&quot;1506&quot; src=&quot;https://blog.shishishi.cc/_astro/node-config-transport.C1FUTCPl_ZQLeSj.webp&quot; srcset=&quot;&quot;&gt;&lt;/figure&gt;
&lt;p&gt;If you want to configure port hopping and server binding, this is the relevant part of the interface:&lt;/p&gt;
&lt;figure style=&quot;text-align: center;&quot; data-lightbox=&quot;article&quot;&gt;&lt;img alt=&quot;The Hysteria hop interval and server binding configuration in Xboard&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1152&quot; height=&quot;1510&quot; src=&quot;https://blog.shishishi.cc/_astro/node-config-hop-binding.Bhb1avyz_ZvbXdD.webp&quot; srcset=&quot;&quot;&gt;&lt;/figure&gt;
&lt;h3 id=&quot;10-install-xboard-node&quot;&gt;10. Install Xboard-Node&lt;/h3&gt;
&lt;p&gt;You can go straight to the Xboard backend, open &lt;code&gt;Node Management -&amp;gt; Add Server&lt;/code&gt;, copy the generated xboard-node installation command, and run it on the node server.&lt;/p&gt;
&lt;figure style=&quot;text-align: center;&quot; data-lightbox=&quot;article&quot;&gt;&lt;img alt=&quot;The Xboard dialog that generates a server token and install command&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3018&quot; height=&quot;1576&quot; src=&quot;https://blog.shishishi.cc/_astro/server-token-install-dialog.CtsRUXgz_bdJes.webp&quot; srcset=&quot;&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Once installation is done, the backend should show the server online with resource and load information.&lt;/p&gt;
&lt;figure style=&quot;text-align: center;&quot; data-lightbox=&quot;article&quot;&gt;&lt;img alt=&quot;The Xboard server management page showing online node status&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2388&quot; height=&quot;1000&quot; src=&quot;https://blog.shishishi.cc/_astro/server-status-overview.BMF0Keqf_Z10Dgan.webp&quot; srcset=&quot;&quot;&gt;&lt;/figure&gt;
&lt;h3 id=&quot;11-configure-hy2-certificates&quot;&gt;11. Configure HY2 Certificates&lt;/h3&gt;
&lt;p&gt;HY2 requires TLS certificates.&lt;/p&gt;
&lt;div class=&quot;tip&quot; data-callout=&quot;tip&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Troubleshooting&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;If you are already seeing &lt;code&gt;protocol &quot;hysteria&quot; requires TLS certificate files&lt;/code&gt;, jump to &lt;a href=&quot;#pitfall-hy2-cert&quot;&gt;Server online but node not running&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If the node server has nothing listening on port 80, you can issue a certificate directly:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -y&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; certbot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; certbot&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; certonly&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --standalone&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -d&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; hk.474769754.xyz&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  --agree-tos&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  -m&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; admin@474769754.xyz&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  --non-interactive&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If Caddy or nginx is already bound to port 80 on that machine, stop it first, issue the certificate, then start it again:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; stop&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; certbot&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; certonly&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --standalone&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -d&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; hk.474769754.xyz&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --agree-tos&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; admin@474769754.xyz&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --non-interactive&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The resulting certificate files are:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/etc/letsencrypt/live/hk.474769754.xyz/fullchain.pem&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/etc/letsencrypt/live/hk.474769754.xyz/privkey.pem&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy them into the Xboard-Node directory:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; mkdir&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/xboard-node/certs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 0644&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/letsencrypt/live/hk.474769754.xyz/fullchain.pem&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#98C379&quot;&gt;  /etc/xboard-node/certs/hk.fullchain.pem&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 0600&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/letsencrypt/live/hk.474769754.xyz/privkey.pem&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#98C379&quot;&gt;  /etc/xboard-node/certs/hk.privkey.pem&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit the config:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nano&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/xboard-node/config.yml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add this at the top level:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;cert&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  cert_mode&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  domain&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;hk.474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  cert_file&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/etc/xboard-node/certs/hk.fullchain.pem&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  key_file&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/etc/xboard-node/certs/hk.privkey.pem&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;cert:&lt;/code&gt; must stay at the top level. Do not indent it under &lt;code&gt;panel:&lt;/code&gt; or &lt;code&gt;machine:&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Restart the service:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; restart&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; xboard-node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; journalctl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; xboard-node&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --since&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;2 minutes ago&quot;&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --no-pager&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the UDP listener:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ss&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -lunp&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 8443&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see &lt;code&gt;8443/udp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The US node is the same idea. Just replace the domain and file paths with:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;us.474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/etc/xboard-node/certs/us.fullchain.pem&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/etc/xboard-node/certs/us.privkey.pem&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;12-firewall-and-security-groups&quot;&gt;12. Firewall and Security Groups&lt;/h3&gt;
&lt;p&gt;Open these ports on the panel server:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;80/tcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;443/tcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;22/tcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open these on the node server:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;8443/udp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;22/tcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are using port hopping:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20000-29999/udp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example &lt;code&gt;ufw&lt;/code&gt; rules:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ufw&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; allow&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; 22/tcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ufw&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; allow&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; 80/tcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ufw&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; allow&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; 443/tcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ufw&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; allow&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; 8443/udp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ufw&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; enable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ufw&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; status&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your cloud provider has security groups, open the same UDP ports there too. Opening only the system firewall is not enough.&lt;/p&gt;
&lt;h3 id=&quot;13-hy2-port-hopping&quot;&gt;13. HY2 Port Hopping&lt;/h3&gt;
&lt;p&gt;Hysteria2 supports port hopping. A practical range is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20000-29999/udp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Xboard node settings would look like:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Connect port: 20000-29999&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Service port: 8443&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Hop interval: 30&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the node server, redirect the port range to the real listening port:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nat&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; PREROUTING&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --dport&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; 20000:29999&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; REDIRECT&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --to-ports&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 8443&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Persist the rule:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -y&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; iptables-persistent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; netfilter-persistent&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check both iptables and the UDP listener:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nat&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -S&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; PREROUTING&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 8443&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ss&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -lunp&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 8443&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;During rollout, it is safer to keep both open:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;8443/udp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20000-29999/udp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once everything is stable, you can keep only the hopping range.&lt;/p&gt;
&lt;div class=&quot;tip&quot; data-callout=&quot;tip&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Troubleshooting&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;If you are worried that &lt;code&gt;ss&lt;/code&gt; does not show the full port range, jump to &lt;a href=&quot;#pitfall-port-hop&quot;&gt;UDP port range not visible&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;14-user-subscriptions-and-clients&quot;&gt;14. User Subscriptions and Clients&lt;/h3&gt;
&lt;p&gt;The usual flow is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Create a plan -&amp;gt; create a user -&amp;gt; attach the plan -&amp;gt; copy the subscription link -&amp;gt; import into the client&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the imported client shows only &lt;code&gt;DIRECT&lt;/code&gt; and no nodes, the common reasons are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The user does not have a valid plan.&lt;/li&gt;
&lt;li&gt;The plan’s permission group does not include the node’s permission group.&lt;/li&gt;
&lt;li&gt;The node is marked hidden.&lt;/li&gt;
&lt;li&gt;The node service is not running.&lt;/li&gt;
&lt;li&gt;The client does not support Hysteria2.&lt;/li&gt;
&lt;li&gt;The subscription template does not include HY2.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Inspect the subscription payload directly:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -L&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;https://474769754.xyz/s/your-token&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the output does not contain:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;hysteria&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;hy2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;hk.474769754.xyz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;US-HY2-01&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then Xboard is not distributing the node yet, and the first things to check are plans and permission groups.&lt;/p&gt;
&lt;p&gt;If the payload does contain nodes but the client still does not show them, switch to a client or template that supports HY2, such as sing-box, Clash Meta, or Mihomo.&lt;/p&gt;
&lt;h3 id=&quot;15-pre-launch-checklist&quot;&gt;15. Pre-Launch Checklist&lt;/h3&gt;
&lt;p&gt;Before enabling public registration, I recommend confirming all of the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SMTP test mail works&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Email verification enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Registration captcha enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Login captcha enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Admin path changed to a random one&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Subscription domain only exposes /s/*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Plan permission groups are correct&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Node multipliers are correct&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HK node uses a higher multiplier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Cloud security groups expose only necessary ports&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suggested trial policy:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Free signup plan: disabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;or 1 GB / 1 day&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Do not allocate too much Hong Kong traffic to cheap plans&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;16-final-hardening&quot;&gt;16. Final Hardening&lt;/h3&gt;
&lt;p&gt;The final port exposure I recommend is:&lt;/p&gt;
&lt;p&gt;Panel server:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;80/tcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;443/tcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;22/tcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Node server:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;22/tcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20000-29999/udp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are not using port hopping:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;22/tcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;8443/udp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Caddy should expose only:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Full reverse proxy for the panel domain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Only /s/* and required API paths on the subscription domain&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Backend settings:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Open registration: only when needed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Email verification: enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Captcha: enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Admin path: random&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Trial quota: extremely low or disabled&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Node strategy:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;US as the primary route, 1x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HK as the backup route, 5x / 8x / 10x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At that point you are no longer in a test setup. You have something you can reasonably expose and operate.&lt;/p&gt;
&lt;h2 id=&quot;pitfalls&quot;&gt;Pitfalls&lt;/h2&gt;
&lt;p&gt;&lt;a id=&quot;pitfall-apt-source&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;broken-apt-source&quot;&gt;Broken apt source&lt;/h3&gt;
&lt;p&gt;If you run into:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;E: The repository &apos;https://packagecloud.io/ookla/speedtest-cli/ubuntu noble Release&apos; does not have a Release file.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;that means the Ookla speedtest apt source does not support the current system release. Disable it first:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;packagecloud.io/ookla&quot;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/apt/sources.list&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/apt/sources.list.d/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suppose the output is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/etc/apt/sources.list.d/ookla_speedtest-cli.list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Disable it like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; mv&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/apt/sources.list.d/ookla_speedtest-cli.list&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#98C379&quot;&gt;  /etc/apt/sources.list.d/ookla_speedtest-cli.list.disabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; update&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was a real issue in my own deployment. Any installer that calls &lt;code&gt;apt-get update&lt;/code&gt; can get stuck behind that broken source.&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;pitfall-fake-ip&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;fake-ip-interference&quot;&gt;Fake-IP interference&lt;/h3&gt;
&lt;p&gt;If your machine is running a proxy or TUN, &lt;code&gt;198.18.x.x&lt;/code&gt; fake IP results are normal. Use a public DNS resolver directly, or temporarily disable the proxy while checking records.&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;pitfall-tls-eof&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;caddy-tls-eof&quot;&gt;Caddy TLS EOF&lt;/h3&gt;
&lt;p&gt;Typical symptom:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;curl: (35) TLS connect error: unexpected eof while reading&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check with:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;nslookup&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; domain&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 1.1.1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; validate&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --config&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/caddy/Caddyfile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; journalctl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -f&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The common causes are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DNS is not pointing at the server.&lt;/li&gt;
&lt;li&gt;The Caddyfile never made it onto the server.&lt;/li&gt;
&lt;li&gt;Caddy was not reloaded.&lt;/li&gt;
&lt;li&gt;Ports &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt; are not actually open.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a id=&quot;pitfall-subscription-404&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;subscription-404&quot;&gt;Subscription 404&lt;/h3&gt;
&lt;p&gt;If the response headers look like:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HTTP/2 404&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;server: Caddy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;content-length: 9&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then Caddy is blocking the request. Make sure the Caddyfile includes:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;handle /s/* {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	reverse_proxy 127.0.0.1:7001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a id=&quot;pitfall-smtp-unauthorized&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;smtp-unauthorized-ip&quot;&gt;SMTP unauthorized IP&lt;/h3&gt;
&lt;p&gt;Check the logs:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;SendEmailJob\|SMTP\|Unauthorized&quot;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /opt/Xboard/storage/logs/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you find:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;525 5.7.1 Unauthorized IP address&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then Brevo is rejecting the outbound IP address. Check the IP used by the Xboard container:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /opt/Xboard&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; docker&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; exec&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -T&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; xboard&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -s&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://ifconfig.me&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add that IP to Brevo’s &lt;code&gt;Authorized IP addresses&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;pitfall-hy2-cert&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;server-online-but-node-not-running&quot;&gt;Server online but node not running&lt;/h3&gt;
&lt;p&gt;Inspect the node logs:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; journalctl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; xboard-node&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 120&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --no-pager&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;requires TLS certificate files&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then HY2 is missing certificate configuration. Go back to Section 11 and add the &lt;code&gt;cert:&lt;/code&gt; block correctly.&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;pitfall-node-idle&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;node-running-but-idle&quot;&gt;Node running but idle&lt;/h3&gt;
&lt;p&gt;This is normal. It simply means the node process is up, but no client is actively using it at the moment.&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;pitfall-port-hop&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;udp-port-range-not-visible&quot;&gt;UDP port range not visible&lt;/h3&gt;
&lt;p&gt;Check:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ss&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -lunp&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 8443&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are using port hopping, it is normal that &lt;code&gt;ss&lt;/code&gt; shows only &lt;code&gt;8443&lt;/code&gt;, because the process is actually listening on that single backend port. Use iptables to inspect the forwarding rule:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nat&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -S&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; PREROUTING&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 20000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also remember to open &lt;code&gt;20000-29999/udp&lt;/code&gt; in your cloud security group.&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;pitfall-delete-plan&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;cannot-delete-a-plan&quot;&gt;Cannot delete a plan&lt;/h3&gt;
&lt;p&gt;If the backend says that the plan cannot be deleted because orders still reference it, do not force-delete it.&lt;/p&gt;
&lt;p&gt;A safer pattern is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Disable the plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Hide the plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Close purchases&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Rename it to Test-Deprecated&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keeping historical order references intact is normal in production systems.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cedar2025/Xboard/tree/compose&quot;&gt;Xboard compose branch and &lt;code&gt;ghcr.io/cedar2025/xboard:latest&lt;/code&gt; image&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://raw.githubusercontent.com/cedar2025/Xboard/compose/compose.yaml&quot;&gt;Official compose file with host &lt;code&gt;7001&lt;/code&gt; mapped to container &lt;code&gt;7001&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cedar2025/Xboard-Node&quot;&gt;Xboard-Node with Hysteria2, machine mode, node mode, and install script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v2.hysteria.network/docs/advanced/Port-Hopping/&quot;&gt;Hysteria2 Port Hopping documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/email-service/get-started/&quot;&gt;Cloudflare Email Routing documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://help.brevo.com/hc/en-us/articles/7924908994450-Send-transactional-emails-using-Brevo-SMTP&quot;&gt;Brevo SMTP configuration documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>From Disk to Files: Understanding GPT, APFS, Containers, and Volumes on macOS</title><link>https://blog.shishishi.cc/en/blog/macos-file-system/</link><guid isPermaLink="true">https://blog.shishishi.cc/en/blog/macos-file-system/</guid><description>A macOS-focused explanation of physical disks, GPT partitions, APFS containers, volumes, mount points, and system volume groups.</description><pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The first time you inspect storage on macOS, several terms tend to blur together: disk, partition, container, volume, file system, and mount point. They all sound like parts of a drive, but they live at different layers.&lt;/p&gt;
&lt;p&gt;The main source of confusion is that older storage layouts often looked like “one partition equals one volume.” Modern macOS uses APFS by default, and APFS adds a container layer between partitions and volumes. A single APFS container can hold multiple volumes, and those volumes share the same pool of free space.&lt;/p&gt;
&lt;p&gt;For a typical macOS system disk, the path looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Physical disk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; GPT partition table&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Partition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; APFS container&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; APFS volumes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Files and directories&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a simple exFAT external drive, the structure is usually flatter:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Physical disk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; GPT / MBR partition table&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Partition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; exFAT file system volume&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Files and directories&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once these two paths are clear, names like &lt;code&gt;Macintosh HD&lt;/code&gt;, &lt;code&gt;Macintosh HD - Data&lt;/code&gt;, &lt;code&gt;Preboot&lt;/code&gt;, &lt;code&gt;Recovery&lt;/code&gt;, &lt;code&gt;VM&lt;/code&gt;, &lt;code&gt;/dev/disk0&lt;/code&gt;, &lt;code&gt;/dev/disk0s2&lt;/code&gt;, and &lt;code&gt;disk3&lt;/code&gt; stop looking like a random pile of devices.&lt;/p&gt;
&lt;h2 id=&quot;physical-disks&quot;&gt;Physical Disks&lt;/h2&gt;
&lt;p&gt;A physical disk is the actual storage device: the internal SSD, an external hard drive, a USB drive, an SD card, or an NVMe drive.&lt;/p&gt;
&lt;p&gt;On macOS, it may appear as:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/dev/disk0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/dev/disk4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These names are not stable identities. Plugging in devices, rebooting, or mounting disk images can change the numbering. When you want to inspect the current layout, use:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;diskutil&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the lowest level, a disk is a sequence of addressable blocks. The partition table describes which ranges of those blocks belong to which partitions.&lt;/p&gt;
&lt;h2 id=&quot;gpt-the-partition-map&quot;&gt;GPT: The Partition Map&lt;/h2&gt;
&lt;p&gt;GPT is the partition table format commonly used by modern macOS disks. It does not manage files, and it does not know where your documents, photos, or apps are. It only records how the disk is divided.&lt;/p&gt;
&lt;p&gt;GPT usually records:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where a partition starts.&lt;/li&gt;
&lt;li&gt;Where a partition ends.&lt;/li&gt;
&lt;li&gt;What type the partition is.&lt;/li&gt;
&lt;li&gt;The partition GUID.&lt;/li&gt;
&lt;li&gt;The partition name.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So GPT is a disk-level map. It tells the system “this area is an EFI partition” and “that area is an APFS partition,” but it does not manage the files inside those areas.&lt;/p&gt;
&lt;p&gt;MBR still appears on older devices and in some compatibility scenarios. For current Mac system disks and most modern external disks, GPT is the more common layout.&lt;/p&gt;
&lt;h2 id=&quot;partitions&quot;&gt;Partitions&lt;/h2&gt;
&lt;p&gt;A partition is a continuous range of space on a disk.&lt;/p&gt;
&lt;p&gt;On macOS, names often look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;disk0      the whole disk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;disk0s1    partition 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;disk0s2    partition 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A partition can contain many different things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;APFS&lt;/li&gt;
&lt;li&gt;HFS+&lt;/li&gt;
&lt;li&gt;exFAT&lt;/li&gt;
&lt;li&gt;FAT32&lt;/li&gt;
&lt;li&gt;NTFS&lt;/li&gt;
&lt;li&gt;Linux ext4&lt;/li&gt;
&lt;li&gt;EFI System Partition&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The older mental model is often:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;one partition = one file system = one volume&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A normal exFAT USB drive often works like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GPT partition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; exFAT file system&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; volume named DATA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; files&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In that situation, partition and volume appear almost identical. APFS is different.&lt;/p&gt;
&lt;h2 id=&quot;file-systems&quot;&gt;File Systems&lt;/h2&gt;
&lt;p&gt;A file system defines how files are stored, how directories are organized, how permissions are recorded, and how free space is managed.&lt;/p&gt;
&lt;p&gt;Common file systems include:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File system&lt;/th&gt;
&lt;th&gt;Common platform&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;APFS&lt;/td&gt;
&lt;td&gt;macOS, iOS, iPadOS&lt;/td&gt;
&lt;td&gt;Apple’s modern file system, with space sharing, snapshots, clones, encryption, and more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HFS+&lt;/td&gt;
&lt;td&gt;Older macOS versions&lt;/td&gt;
&lt;td&gt;Apple’s pre-APFS file system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NTFS&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Common for Windows system drives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exFAT&lt;/td&gt;
&lt;td&gt;Cross-platform&lt;/td&gt;
&lt;td&gt;Useful for USB drives and external disks shared between macOS and Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FAT32&lt;/td&gt;
&lt;td&gt;Older devices&lt;/td&gt;
&lt;td&gt;Very compatible, but limited to 4 GB per file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ext4&lt;/td&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Common Linux file system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Btrfs&lt;/td&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Supports modern features such as snapshots and checksumming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XFS&lt;/td&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Common for large files and server workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The file system affects file naming rules, maximum file size, permissions, snapshots, case sensitivity, journaling, copy-on-write behavior, and more.&lt;/p&gt;
&lt;p&gt;On current macOS, APFS is the most important file system to understand. It is not just a format; it also brings containers, shared space, volume groups, and snapshots into daily storage management.&lt;/p&gt;
&lt;h2 id=&quot;apfs-containers&quot;&gt;APFS Containers&lt;/h2&gt;
&lt;p&gt;The key APFS concept is the container.&lt;/p&gt;
&lt;p&gt;A GPT partition can be used as APFS physical storage. Inside that storage, macOS creates an APFS container. The container can then hold multiple APFS volumes.&lt;/p&gt;
&lt;p&gt;A typical layout looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Physical disk disk0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; GPT partition disk0s2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -&amp;gt; APFS Container disk3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      -&amp;gt; Macintosh HD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      -&amp;gt; Macintosh HD - Data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      -&amp;gt; Preboot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      -&amp;gt; Recovery&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      -&amp;gt; VM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;disk3&lt;/code&gt; is not another physical disk. It is a synthesized APFS container device created by macOS.&lt;/p&gt;
&lt;p&gt;The major benefit of an APFS container is space sharing. If a 1 TB APFS container holds two volumes, and volume A uses 200 GB while volume B uses 100 GB, the remaining space is not permanently assigned to either volume. It remains free space inside the container and can be allocated to any volume as needed.&lt;/p&gt;
&lt;p&gt;That is very different from traditional partitioning. If you split a disk into two fixed 500 GB partitions, unused space in partition A is not automatically available to partition B. APFS containers are more flexible: multiple volumes share the same free space pool.&lt;/p&gt;
&lt;p&gt;APFS volumes can still have boundaries when needed. You can set a reserve size to guarantee space, or a quota size to limit how much a volume can allocate.&lt;/p&gt;
&lt;h2 id=&quot;volumes&quot;&gt;Volumes&lt;/h2&gt;
&lt;p&gt;A volume is the logical storage unit that the operating system can mount, name, display, and use for files.&lt;/p&gt;
&lt;p&gt;In Finder, names like these are usually volumes:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Macintosh HD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Macintosh HD - Data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Backup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;MyDisk&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the command line, external volumes are usually mounted under:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/Volumes/Backup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows drive letters such as &lt;code&gt;C:&lt;/code&gt; and &lt;code&gt;D:&lt;/code&gt; can also be understood as volumes. On Linux, paths like &lt;code&gt;/mnt/data&lt;/code&gt; or &lt;code&gt;/media/xxx&lt;/code&gt; are mount points where volumes become accessible.&lt;/p&gt;
&lt;p&gt;So a volume is the usable operating-system-level unit. A partition is a continuous range on a disk. An APFS container is the shared space pool that holds multiple APFS volumes.&lt;/p&gt;
&lt;h2 id=&quot;mounting&quot;&gt;Mounting&lt;/h2&gt;
&lt;p&gt;Mounting means attaching a volume to the operating system’s directory tree. If an external volume is named &lt;code&gt;Backup&lt;/code&gt;, macOS usually mounts it at:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/Volumes/Backup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a volume is not mounted, it may still exist and its file system may still be intact, but Finder will not show it and you cannot access its files through a normal path.&lt;/p&gt;
&lt;p&gt;Useful commands include:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;diskutil&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; mount&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /dev/disk4s2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;diskutil&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; unmount&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /dev/disk4s2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;diskutil&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; unmountDisk&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /dev/disk4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;unmount&lt;/code&gt; detaches a specific volume or partition. &lt;code&gt;unmountDisk&lt;/code&gt; detaches all unmountable volumes on a disk.&lt;/p&gt;
&lt;h2 id=&quot;why-macintosh-hd-and-macintosh-hd---data-are-separate&quot;&gt;Why Macintosh HD and Macintosh HD - Data Are Separate&lt;/h2&gt;
&lt;p&gt;Starting with macOS Catalina, Apple separated system files from user data. Roughly:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Macintosh HD          system volume&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Macintosh HD - Data   data volume&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The system volume stores macOS system content. On modern macOS, it is read-only. Starting with macOS Big Sur, it became a Signed System Volume, meaning macOS verifies the integrity of system content. If verification fails, the boot process may stop or require macOS to be reinstalled.&lt;/p&gt;
&lt;p&gt;The data volume stores user files, app data, and writable configuration. macOS uses APFS Volume Groups and firmlinks to present the system volume and data volume as one coherent system.&lt;/p&gt;
&lt;p&gt;So Finder gives you the feeling of one unified disk, but underneath it, two volumes have different responsibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The system volume keeps operating system content stable and verifiable.&lt;/li&gt;
&lt;li&gt;The data volume stores user and app-generated writable data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is why &lt;code&gt;Macintosh HD - Data&lt;/code&gt; should not be treated as a duplicate disk. It is part of the modern macOS file management model.&lt;/p&gt;
&lt;h2 id=&quot;apfs-volume-groups&quot;&gt;APFS Volume Groups&lt;/h2&gt;
&lt;p&gt;An APFS Volume Group is a set of related APFS volumes.&lt;/p&gt;
&lt;p&gt;The most common pair is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Macintosh HD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Macintosh HD - Data&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Together, they form the system volume group. This design lets macOS do two things at the same time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep system content isolated, read-only, and verifiable.&lt;/li&gt;
&lt;li&gt;Keep user data writable and available at familiar paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The user sees one system. Internally, macOS knows which content belongs to the system volume and which content comes from the data volume.&lt;/p&gt;
&lt;h2 id=&quot;preboot-recovery-and-vm&quot;&gt;Preboot, Recovery, and VM&lt;/h2&gt;
&lt;p&gt;An APFS system container often contains several system-managed volumes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Preboot&lt;/code&gt;: Stores information needed to boot APFS system volumes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Recovery&lt;/code&gt;: Stores the recovery environment for reinstalling macOS, Disk Utility, Terminal, and system recovery.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VM&lt;/code&gt;: Stores virtual memory, swap files, and sleep-related data.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Update&lt;/code&gt;: Appears on some macOS versions and participates in system updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These volumes are managed by macOS and should not be deleted manually. &lt;code&gt;Preboot&lt;/code&gt; and &lt;code&gt;Recovery&lt;/code&gt; may look unimportant during normal use, but they matter when the system needs to boot or recover.&lt;/p&gt;
&lt;h2 id=&quot;formatting-erasing-deleting-volumes-and-deleting-partitions&quot;&gt;Formatting, Erasing, Deleting Volumes, and Deleting Partitions&lt;/h2&gt;
&lt;p&gt;Disk Utility uses several similar-looking operations, but they happen at different layers.&lt;/p&gt;
&lt;p&gt;Formatting usually means creating a new file system on a partition or volume. For example:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;diskutil&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; eraseVolume&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ExFAT&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; DATA&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /dev/disk4s2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates an exFAT file system on &lt;code&gt;/dev/disk4s2&lt;/code&gt; and names the volume &lt;code&gt;DATA&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Erasing a whole disk can be much more destructive. If you select an entire physical disk, macOS may rebuild the partition table, create new partitions, create a file system, and create a volume:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;diskutil&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; eraseDisk&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ExFAT&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; DATA&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; GPT&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /dev/disk4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That affects the entire disk and removes the old partition layout.&lt;/p&gt;
&lt;p&gt;Deleting an APFS volume is also different from deleting an APFS partition.&lt;/p&gt;
&lt;p&gt;After deleting an APFS volume, the freed space returns to the APFS container:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;APFS Container&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Volume B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Free space inside container&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After deleting an APFS partition, the space returns to the GPT layer as unallocated disk space:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; EFI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Free Space&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Other Partition&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This explains a common surprise: deleting an APFS volume may not create visible unallocated space in the partition map. You removed a volume inside the container, not the GPT partition itself.&lt;/p&gt;
&lt;h2 id=&quot;snapshots&quot;&gt;Snapshots&lt;/h2&gt;
&lt;p&gt;APFS supports snapshots. A snapshot records the state of a file system at a point in time. It does not immediately copy every file.&lt;/p&gt;
&lt;p&gt;APFS uses copy-on-write. When a snapshot is first created, it uses very little additional space. Later, when files change, old blocks are kept so the snapshot can still see the previous state.&lt;/p&gt;
&lt;p&gt;This is part of what makes Time Machine, system update rollback, and recovery workflows efficient.&lt;/p&gt;
&lt;p&gt;Snapshots can still consume space. If you delete files but free space does not seem to return immediately, local snapshots are one of the things worth checking, along with Trash, caches, and cloud sync behavior.&lt;/p&gt;
&lt;h2 id=&quot;efi-partition&quot;&gt;EFI Partition&lt;/h2&gt;
&lt;p&gt;GPT disks often include an EFI System Partition, also called ESP. It usually uses FAT32 and stores UEFI boot-related files.&lt;/p&gt;
&lt;p&gt;macOS may create an EFI partition even on some external disks, even if that disk is not actively used for booting. Do not casually delete the EFI partition on a system disk. It is quiet most of the time, but it belongs to the boot chain.&lt;/p&gt;
&lt;h2 id=&quot;raid-and-volumes&quot;&gt;RAID and Volumes&lt;/h2&gt;
&lt;p&gt;RAID combines multiple disks into a logical storage device. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RAID 0: Striping for performance, no redundancy.&lt;/li&gt;
&lt;li&gt;RAID 1: Mirroring for redundancy.&lt;/li&gt;
&lt;li&gt;RAID 5: Redundancy with parity.&lt;/li&gt;
&lt;li&gt;RAID 10: Mirroring plus striping.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After RAID is created, the operating system usually sees a new logical disk. That logical disk can still contain a partition table, partitions, file systems, and volumes.&lt;/p&gt;
&lt;p&gt;RAID does not replace the file system. It operates below it by combining physical disks into a logical device.&lt;/p&gt;
&lt;h2 id=&quot;reading-diskutil-output&quot;&gt;Reading diskutil Output&lt;/h2&gt;
&lt;p&gt;On macOS, these clues help decode storage output:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Usually means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/dev/disk4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A whole disk, which may be physical or synthesized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/dev/disk4s2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Partition 2 on disk4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GUID_partition_scheme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The disk uses GPT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EFI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EFI System Partition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Apple_APFS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A partition used as APFS physical storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APFS Container&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;APFS container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APFS Volume&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A volume inside an APFS container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Free Space&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unallocated space at the GPT layer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This command is especially useful for APFS:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;diskutil&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apfs&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It shows APFS containers, volumes, volume groups, snapshots, and related details more clearly than the generic disk list.&lt;/p&gt;
&lt;h2 id=&quot;the-core-idea&quot;&gt;The Core Idea&lt;/h2&gt;
&lt;p&gt;The most important macOS takeaway is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;APFS is not simply &quot;one partition, one volume.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;It creates a container inside a partition, then manages multiple shared-space volumes inside that container.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once that is clear, &lt;code&gt;Macintosh HD&lt;/code&gt;, &lt;code&gt;Macintosh HD - Data&lt;/code&gt;, &lt;code&gt;Preboot&lt;/code&gt;, &lt;code&gt;Recovery&lt;/code&gt;, and &lt;code&gt;VM&lt;/code&gt; no longer look like mysterious extra drives. They are pieces of the storage model macOS uses to keep the system reliable, recoverable, and easier to manage.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://support.apple.com/guide/disk-utility/add-delete-or-erase-apfs-volumes-dskua9e6a110/mac&quot;&gt;Disk Utility: Add, delete, or erase APFS volumes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.apple.com/guide/security/signed-system-volume-security-secd698747c9/web&quot;&gt;Apple Platform Security: Signed system volume security&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Deploying a QQ Bot on Linux with NapCat, NoneBot2, GsCore, and Caddy</title><link>https://blog.shishishi.cc/en/blog/linux-qq-bot-napcat-nonebot-gscore-caddy/</link><guid isPermaLink="true">https://blog.shishishi.cc/en/blog/linux-qq-bot-napcat-nonebot-gscore-caddy/</guid><description>A practical end-to-end deployment guide covering NapCat, NoneBot2, GsCore, and Caddy, including the real issues we hit and how we fixed them.</description><pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I wanted a QQ bot setup on Linux that was not just “working for now,” but stable enough to live with. The stack I ended up with was &lt;code&gt;NapCat + NoneBot2 + GsCore&lt;/code&gt;, with &lt;code&gt;Caddy&lt;/code&gt; in front for HTTPS and reverse proxying. The overall design is straightforward. The actual deployment, though, was shaped by a series of very real and very ordinary problems: terminal encoding, missing CLI tools, local-only bindings, TLS validation, and even something as basic as forgotten DNS records.&lt;/p&gt;
&lt;p&gt;This post turns that whole session into a practical guide you can follow from scratch. More importantly, it includes the failures along the way, because those are usually the parts that save the most time later.&lt;/p&gt;
&lt;h2 id=&quot;the-final-architecture&quot;&gt;The Final Architecture&lt;/h2&gt;
&lt;p&gt;This is the message path we ended up with:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;QQ / group message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; NapCat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; NoneBot2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; nonebot-plugin-genshinuid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; GsCore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; actual business plugin (such as GenshinUID)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For admin access from the web, we added another layer:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Browser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Caddy (HTTPS / reverse proxy)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; NapCat WebUI / GsCore WebConsole&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two design decisions mattered a lot here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Treat the bot message path and the admin panel path as separate concerns.&lt;/li&gt;
&lt;li&gt;Avoid exposing &lt;code&gt;6099&lt;/code&gt; and &lt;code&gt;8765&lt;/code&gt; directly to the public internet when a reverse proxy can keep them behind localhost.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;what-each-piece-does&quot;&gt;What Each Piece Does&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NapCat&lt;/code&gt;: the protocol side and QQ login layer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NoneBot2&lt;/code&gt;: the bot framework itself, responsible for receiving OneBot events and dispatching logic.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GsCore&lt;/code&gt;: the Sayu core layer that provides the plugin ecosystem and a unified connection model.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nonebot-plugin-genshinuid&lt;/code&gt;: the official NoneBot2-side plugin used to connect to GsCore.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Caddy&lt;/code&gt;: the HTTPS and reverse proxy layer for safely exposing the admin panels.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;environment-assumptions&quot;&gt;Environment Assumptions&lt;/h2&gt;
&lt;p&gt;This guide assumes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You are on Ubuntu, Debian, or a similar Linux system.&lt;/li&gt;
&lt;li&gt;You have &lt;code&gt;root&lt;/code&gt; or &lt;code&gt;sudo&lt;/code&gt; access.&lt;/li&gt;
&lt;li&gt;The server has internet access.&lt;/li&gt;
&lt;li&gt;You have a QQ account ready for bot login.&lt;/li&gt;
&lt;li&gt;You have a domain name and plan to manage DNS with Cloudflare.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The directory layout in this guide looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/qqbot        # NoneBot2 project&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/gsuid_core   # GsCore installation&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;step-1-install-napcat&quot;&gt;Step 1: Install NapCat&lt;/h2&gt;
&lt;p&gt;NapCat provides a Linux installer, and the straightforward way to start is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; napcat.sh&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://nclatest.znin.net/NapNeko/NapCat-Installer/main/script/install.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;bash&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; napcat.sh&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --tui&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After installation, you can usually re-enter its TUI with:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; napcat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;problem-1-napcat-shell-showed-garbled-text&quot;&gt;Problem 1: NapCat Shell showed garbled text&lt;/h3&gt;
&lt;p&gt;The first thing we saw was not a clean UI, but broken characters like &lt;code&gt;~@~A&lt;/code&gt; and &lt;code&gt;~M~U&lt;/code&gt;. This turned out not to be a NapCat bug at all. It was a classic locale and terminal issue.&lt;/p&gt;
&lt;p&gt;Start by checking:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;locale&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt; $LANG&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt; $TERM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the environment is not using UTF-8, fix it temporarily before launching NapCat:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt; LANG&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;zh_CN&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt; LC_ALL&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;zh_CN&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt; TERM&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;xterm-256color&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; napcat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the server does not have the locale generated yet, install it:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -y&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; locales&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; locale-gen&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; zh_CN.UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; update-locale&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; LANG=zh_CN.UTF-8&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you prefer a neutral UTF-8 setup instead of a Chinese locale, &lt;code&gt;C.UTF-8&lt;/code&gt; works well too:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt; LANG&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;C&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt; LC_ALL&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;C&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt; TERM&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;xterm-256color&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; napcat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;log-in-to-napcat-webui&quot;&gt;Log in to NapCat WebUI&lt;/h3&gt;
&lt;p&gt;NapCat’s WebUI typically listens on port &lt;code&gt;6099&lt;/code&gt;. Once it starts, check the logs for the access URL and token, scan the QQ login QR code, and set a proper WebUI password.&lt;/p&gt;
&lt;h2 id=&quot;step-2-create-the-nonebot2-project&quot;&gt;Step 2: Create the NoneBot2 Project&lt;/h2&gt;
&lt;p&gt;Create a Python virtual environment first:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ~/qqbot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ~/qqbot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;python&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; venv&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; .venv&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --prompt&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nonebot2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; .venv/bin/activate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the base dependencies:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;pip&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;nonebot2[fastapi]&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;pip&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nonebot-adapter-onebot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;dotenv&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;HOST&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;127.0.0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;ONEBOT_ACCESS_TOKEN&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;replace-this-with-a-long-random-token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;COMMAND_START&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;COMMAND_SEP&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create &lt;code&gt;bot.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; nonebot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; nonebot.adapters.onebot.v11 &lt;/span&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; Adapter &lt;/span&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; OneBotV11Adapter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;nonebot.&lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;driver &lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; nonebot.&lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;get_driver&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;driver.&lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;register_adapter&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;(OneBotV11Adapter)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C678DD&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt; __name__&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;__main__&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;    nonebot.&lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test that it starts:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; .venv/bin/activate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;python&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; bot.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;step-3-connect-napcat-to-nonebot2&quot;&gt;Step 3: Connect NapCat to NoneBot2&lt;/h2&gt;
&lt;p&gt;In NapCat, create a new &lt;code&gt;WebSocket client&lt;/code&gt; network connection and point it to:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ws://127.0.0.1:8080/onebot/v11/ws&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use the same token value as &lt;code&gt;ONEBOT_ACCESS_TOKEN&lt;/code&gt; in your &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;If you get a &lt;code&gt;403&lt;/code&gt;, the first thing to verify is the token value. A mismatch there is by far the most common cause.&lt;/p&gt;
&lt;h2 id=&quot;step-4-install-gscore&quot;&gt;Step 4: Install GsCore&lt;/h2&gt;
&lt;p&gt;According to the official GsCore docs, install it as a separate project next to your bot:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ~&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://github.com/Genshin-bots/gsuid_core.git&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --depth=1&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --single-branch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; gsuid_core&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The official docs recommend &lt;code&gt;uv&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;uv&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; python&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 3.13&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;uv&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; sync&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --python&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 3.13&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;uv&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; python&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ensurepip&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start GsCore:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;uv&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; core&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On first startup, it will generate:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/gsuid_core/data/config.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/gsuid_core/data/core_config.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;step-5-connect-gscore-to-nonebot2&quot;&gt;Step 5: Connect GsCore to NoneBot2&lt;/h2&gt;
&lt;p&gt;The official NoneBot2-side connector for GsCore is &lt;code&gt;nonebot-plugin-genshinuid&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;problem-2-nb-was-not-found&quot;&gt;Problem 2: &lt;code&gt;nb&lt;/code&gt; was not found&lt;/h3&gt;
&lt;p&gt;We initially followed the official pattern and ran:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;nb&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; plugin&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nonebot-plugin-genshinuid&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That failed with:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-bash: nb: command not found&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing was wrong with the project. The issue was simply that &lt;code&gt;nb-cli&lt;/code&gt; was not installed. The &lt;code&gt;nb&lt;/code&gt; command is not part of Python itself; it belongs to the NoneBot CLI.&lt;/p&gt;
&lt;p&gt;There are two reasonable fixes.&lt;/p&gt;
&lt;h4 id=&quot;fix-a-install-nb-cli&quot;&gt;Fix A: install &lt;code&gt;nb-cli&lt;/code&gt;&lt;/h4&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ~/qqbot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; .venv/bin/activate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;pip&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nb-cli&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;nb&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; plugin&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nonebot-plugin-genshinuid&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;fix-b-install-the-plugin-directly-with-pip&quot;&gt;Fix B: install the plugin directly with &lt;code&gt;pip&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;If you already manage your bot with a hand-written &lt;code&gt;bot.py&lt;/code&gt;, direct installation is often simpler:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ~/qqbot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; .venv/bin/activate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;pip&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; nonebot-plugin-genshinuid&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That second route is the one I would recommend in this setup. It is shorter and avoids pulling in an extra layer unless you actually want the CLI workflow.&lt;/p&gt;
&lt;h3 id=&quot;add-gscore-settings-to-the-nonebot-env&quot;&gt;Add GsCore settings to the NoneBot &lt;code&gt;.env&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Append these values to your existing &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;dotenv&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;gsuid_core_ws_token&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;123&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;gsuid_core_host&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;localhost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;gsuid_core_port&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;8765&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;gsuid_core_botid&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;NoneBot2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;configure-configjson-on-the-gscore-side&quot;&gt;Configure &lt;code&gt;config.json&lt;/code&gt; on the GsCore side&lt;/h3&gt;
&lt;p&gt;Edit:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/gsuid_core/data/config.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At minimum, make sure these fields are set:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;HOST&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;PORT&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;8765&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;masters&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;your-qq-number&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;WS_TOKEN&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;123&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;TRUSTED_IPS&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two details matter here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WS_TOKEN&lt;/code&gt; must match &lt;code&gt;gsuid_core_ws_token&lt;/code&gt; in the NoneBot &lt;code&gt;.env&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;masters&lt;/code&gt; should include your own QQ number, or a lot of core admin commands will not work for you later&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;step-6-install-the-actual-business-plugin&quot;&gt;Step 6: Install the Actual Business Plugin&lt;/h2&gt;
&lt;p&gt;GsCore by itself is the platform layer. The actual bot features come from business plugins.&lt;/p&gt;
&lt;p&gt;Once your account is listed in &lt;code&gt;masters&lt;/code&gt;, you can install one from chat:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;core安装插件GenshinUID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you prefer manual installation:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ~/gsuid_core/plugins&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -b&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; v4&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://github.com/KimigaiiWuyi/GenshinUID.git&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --depth=1&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --single-branch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart GsCore once the plugin is installed.&lt;/p&gt;
&lt;h2 id=&quot;step-7-access-the-gscore-admin-panel&quot;&gt;Step 7: Access the GsCore Admin Panel&lt;/h2&gt;
&lt;p&gt;GsCore’s admin backend is the Web Console, usually available at:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://127.0.0.1:8765/app&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is enough if you are accessing it locally.&lt;/p&gt;
&lt;h3 id=&quot;problem-3-public-access-returned-502&quot;&gt;Problem 3: public access returned 502&lt;/h3&gt;
&lt;p&gt;When we tried to access the admin panel from the public internet, we ran into &lt;code&gt;HTTP ERROR 502&lt;/code&gt;. The clue came from GsCore itself:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;WebConsole挂载于本地, 如想外网访问请修改data/config.json中host为0.0.0.0!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That message means the web console is bound to localhost only, which is why outside access fails. The most direct fix is to change:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;HOST&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;HOST&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That said, exposing &lt;code&gt;8765&lt;/code&gt; directly is not the design I would recommend. A better pattern is to keep GsCore bound to localhost and let a reverse proxy expose it safely.&lt;/p&gt;
&lt;h2 id=&quot;step-8-put-caddy-in-front-of-napcat-and-gscore&quot;&gt;Step 8: Put Caddy in Front of NapCat and GsCore&lt;/h2&gt;
&lt;p&gt;Operationally, it is cleaner not to expose &lt;code&gt;6099&lt;/code&gt; and &lt;code&gt;8765&lt;/code&gt; to the public internet at all. The safer layout is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Keep NapCat WebUI and GsCore WebConsole bound to localhost&lt;/li&gt;
&lt;li&gt;Expose only &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Let &lt;code&gt;Caddy&lt;/code&gt; handle HTTPS and reverse proxying&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;install-caddy&quot;&gt;Install Caddy&lt;/h3&gt;
&lt;p&gt;On Ubuntu or Debian, one clean installation route is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -y&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; debian-keyring&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; debian-archive-keyring&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt-transport-https&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; curl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -1sLf&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &apos;https://dl.cloudsmith.io/public/caddy/stable/gpg.key&apos;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; gpg&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --dearmor&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /usr/share/keyrings/caddy-stable-archive-keyring.gpg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -1sLf&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &apos;https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt&apos;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; tee&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/apt/sources.list.d/caddy-stable.list&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -y&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;recommended-upstream-layout&quot;&gt;Recommended upstream layout&lt;/h3&gt;
&lt;p&gt;Keep the backend services local:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NapCat WebUI -&amp;gt; &lt;code&gt;127.0.0.1:6099&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GsCore WebConsole -&amp;gt; &lt;code&gt;127.0.0.1:8765&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then configure &lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	email yourmail@example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;napcat.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	reverse_proxy 127.0.0.1:6099&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gscore.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	reverse_proxy 127.0.0.1:8765&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Format, validate, and reload:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; fmt&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --overwrite&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/caddy/Caddyfile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; validate&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --config&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; /etc/caddy/Caddyfile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; restart&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; status&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;step-9-problem-4--caddy-could-not-obtain-certificates&quot;&gt;Step 9: Problem 4 — Caddy could not obtain certificates&lt;/h2&gt;
&lt;p&gt;Caddy itself started just fine, but the logs kept showing &lt;code&gt;challenge failed&lt;/code&gt;. The root cause was not in Caddy at all. I had forgotten to add the DNS records in Cloudflare.&lt;/p&gt;
&lt;p&gt;That type of failure usually comes down to one of these:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The domain does not actually resolve to this server&lt;/li&gt;
&lt;li&gt;Ports &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt; are not publicly reachable&lt;/li&gt;
&lt;li&gt;Another service is already occupying &lt;code&gt;80&lt;/code&gt; or &lt;code&gt;443&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;DNS changes have not propagated yet&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;useful-troubleshooting-commands&quot;&gt;Useful troubleshooting commands&lt;/h3&gt;
&lt;p&gt;Check recent Caddy logs:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; journalctl&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; caddy&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; 80&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; --no-pager&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check DNS resolution:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;dig&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; +short&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; napcat.your-domain.com&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;dig&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; +short&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; gscore.your-domain.com&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; A&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check what is listening:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;ss&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -lntp&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &apos;:80\|:443&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the firewall:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; ufw&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; status&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the DNS record simply does not exist in Cloudflare, ACME validation obviously cannot succeed. It is a basic mistake, but also an extremely common one.&lt;/p&gt;
&lt;h2 id=&quot;the-security-posture-i-would-actually-recommend&quot;&gt;The Security Posture I Would Actually Recommend&lt;/h2&gt;
&lt;p&gt;If the admin panels are mostly for your own use, I would tighten things up this way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Do not expose &lt;code&gt;6099&lt;/code&gt; or &lt;code&gt;8765&lt;/code&gt; directly to the internet&lt;/li&gt;
&lt;li&gt;Expose only &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Keep NapCat and GsCore bound to localhost&lt;/li&gt;
&lt;li&gt;Put &lt;code&gt;Caddy&lt;/code&gt; in front of them&lt;/li&gt;
&lt;li&gt;Keep strong passwords and tokens on the panels themselves&lt;/li&gt;
&lt;li&gt;Add IP allowlisting or another auth layer if you can&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One point is worth stating explicitly: your email address is not an authentication mechanism. It is only a contact address for certificate issuance. The things that actually protect your admin panels are HTTPS, reverse proxy boundaries, panel passwords, tokens, and whether you avoided exposing the raw upstream ports.&lt;/p&gt;
&lt;h2 id=&quot;recommended-startup-order&quot;&gt;Recommended Startup Order&lt;/h2&gt;
&lt;p&gt;Once everything is deployed, I recommend starting and debugging in this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;NapCat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NoneBot2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GsCore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;business plugin&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Caddy&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And when something breaks, trace the same chain in order:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Is QQ login working?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-&amp;gt; Is the OneBot WebSocket connected?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-&amp;gt; Is NoneBot running?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-&amp;gt; Is GsCore connected?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-&amp;gt; Is the plugin loaded?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-&amp;gt; Is the admin panel reachable?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-&amp;gt; Are HTTPS, DNS, and Caddy behaving correctly?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-four-things-most-worth-remembering&quot;&gt;The Four Things Most Worth Remembering&lt;/h2&gt;
&lt;h3 id=&quot;1-garbled-tui-output-usually-means-the-terminal-environment-is-wrong&quot;&gt;1. Garbled TUI output usually means the terminal environment is wrong&lt;/h3&gt;
&lt;p&gt;Check &lt;code&gt;LANG&lt;/code&gt;, &lt;code&gt;LC_ALL&lt;/code&gt;, and &lt;code&gt;TERM&lt;/code&gt; before assuming the software is broken.&lt;/p&gt;
&lt;h3 id=&quot;2-a-missing-nb-command-usually-just-means-nb-cli-is-not-installed&quot;&gt;2. A missing &lt;code&gt;nb&lt;/code&gt; command usually just means &lt;code&gt;nb-cli&lt;/code&gt; is not installed&lt;/h3&gt;
&lt;p&gt;And if you are already managing your bot with a custom &lt;code&gt;bot.py&lt;/code&gt;, direct &lt;code&gt;pip install&lt;/code&gt; may be the simpler path anyway.&lt;/p&gt;
&lt;h3 id=&quot;3-if-a-backend-is-bound-to-localhost-public-access-is-supposed-to-fail&quot;&gt;3. If a backend is bound to localhost, public access is supposed to fail&lt;/h3&gt;
&lt;p&gt;Once you see &lt;code&gt;localhost&lt;/code&gt; bindings or “mounted locally” warnings, stop guessing at public-network issues and fix the binding model first.&lt;/p&gt;
&lt;h3 id=&quot;4-tls-failures-are-often-not-proxy-problems-at-all&quot;&gt;4. TLS failures are often not “proxy problems” at all&lt;/h3&gt;
&lt;p&gt;Even in a stack with reverse proxies, certificates, and HTTPS, the most common failure point is still the boring layer underneath: DNS.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://napneko.github.io/&quot;&gt;NapCat docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nonebot.dev/&quot;&gt;NoneBot2 docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://onebot.adapters.nonebot.dev/&quot;&gt;OneBot adapter docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sayu-bot.com/&quot;&gt;GsCore docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Genshin-bots/gsuid_core&quot;&gt;GsCore repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caddyserver.com/docs/&quot;&gt;Caddy docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caddyserver.com/docs/automatic-https&quot;&gt;Caddy Automatic HTTPS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caddyserver.com/docs/caddyfile/directives/reverse_proxy&quot;&gt;Caddy reverse_proxy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;By the end of this deployment, the biggest takeaway was not “the bot is finally installed.” It was that the hard part of this kind of work is rarely the install commands themselves. What determines whether the setup stays pleasant later is whether the boundaries are clear: whether the protocol layer is separated from the framework, whether admin surfaces are exposed carelessly, whether the domains really point where you think they do, and whether the logs tell you where the failure actually is.&lt;/p&gt;
&lt;p&gt;Once those boundaries are in place, &lt;code&gt;NapCat + NoneBot2 + GsCore + Caddy&lt;/code&gt; is a very workable combination. It is not the lightest stack, and it is not the flashiest one either, but it is clear, stable, and perfectly suited to long-term maintenance.&lt;/p&gt;
</content:encoded></item><item><title>Rebuilding My Personal Server Framework</title><link>https://blog.shishishi.cc/en/blog/server-frame/</link><guid isPermaLink="true">https://blog.shishishi.cc/en/blog/server-frame/</guid><description>Notes on the current entry points, domains, service layers, and maintenance boundaries of my personal server setup.</description><pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While reorganizing my personal servers, the main thing I wanted to solve was not “how do I run a few more services?” It was how to put a growing pile of services back behind clear boundaries.&lt;/p&gt;
&lt;p&gt;HomeLab services tend to pile up naturally: a blog, a start page, monitoring, admin panels, personal projects, bots, Minecraft servers, and all kinds of temporary experiments. At first, each one is just something I want to “get running.” After a while, entry points, domains, databases, ports, and backup strategies start tangling together. When something breaks, it becomes hard to tell which layer failed. When I want to migrate, it becomes hard to move only one part away.&lt;/p&gt;
&lt;p&gt;Looking back, the April version was more like a migration draft: it mapped out the roles of HK Main, the Mac mini, proxy nodes, and each stack. This version is closer to the current scene: which domains are actually in use, which services have become long-lived, and which entry points exist for friends or for my own maintenance.&lt;/p&gt;
&lt;p&gt;So the goal of this framework is straightforward: use one public entry path, split services by responsibility, keep sensitive components inside the private network, put static content on managed hosting whenever possible, and leave the Mac mini for services that need long-running processes, persistence, or private-network access.&lt;/p&gt;
&lt;h2 id=&quot;overall-idea&quot;&gt;Overall Idea&lt;/h2&gt;
&lt;p&gt;The current architecture follows a “Cloudflare as the DNS/access layer + HK Main as the public entry point + Mac mini for local core services” model.&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    User[&quot;Public users / friends&quot;] --&amp;gt; CF[&quot;Cloudflare DNS / CDN&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    CF --&amp;gt; Pages[&quot;Cloudflare Pages&amp;lt;br/&amp;gt;Astro Blog and static content&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    CF --&amp;gt; HK[&quot;HK Main&amp;lt;br/&amp;gt;frps only&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    HK --&amp;gt; FRPC[&quot;Mac mini&amp;lt;br/&amp;gt;frpc&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    FRPC --&amp;gt; NPM[&quot;Nginx Proxy Manager&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    NPM --&amp;gt; Monitor[&quot;monitor-stack&amp;lt;br/&amp;gt;Komari&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    NPM --&amp;gt; Home[&quot;homepage-stack&amp;lt;br/&amp;gt;Homepage&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    NPM --&amp;gt; Private[&quot;private-services-stack&amp;lt;br/&amp;gt;Nextcloud / Vault&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    NPM --&amp;gt; VPN[&quot;vpn-stack&amp;lt;br/&amp;gt;VPN download and config entry&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Friends[&quot;Trusted friends&quot;] --&amp;gt; GameAccess[&quot;Tailscale first&amp;lt;br/&amp;gt;frp / domain fallback&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    GameAccess --&amp;gt; Games[&quot;game-stack&amp;lt;br/&amp;gt;Game Server&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    VPN --&amp;gt; Nodes[&quot;Proxy / VPN nodes&amp;lt;br/&amp;gt;connected as needed&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key idea is to separate “entry” from “business services.”&lt;/p&gt;
&lt;p&gt;HK Main is only the public entry server. It runs &lt;code&gt;frps&lt;/code&gt;, but it does not host databases, personal files, long-running business services, or proxy exit traffic. The real services run locally on the Mac mini inside OrbStack. &lt;code&gt;frpc&lt;/code&gt; connects them to HK Main, and Nginx Proxy Manager (NPM) handles domain-based routing and reverse proxying.&lt;/p&gt;
&lt;p&gt;The static blog still belongs on a hosted platform such as Cloudflare Pages. But &lt;code&gt;homepage.shishishi3.com&lt;/code&gt; is no longer just a static home page; it is the HomeLab navigation and status entry point, so it now lives on the Mac mini as a long-running service. This keeps the static publishing surface and the private service surface from dragging each other down.&lt;/p&gt;
&lt;h2 id=&quot;server-roles&quot;&gt;Server Roles&lt;/h2&gt;
&lt;p&gt;The Mac mini is the core host of this system. It has no public IP. Its job is to run long-lived services, databases, personal projects, bots, and game servers. Containers are split into Docker Compose stacks, each with its own config directory, data directory, and network boundary.&lt;/p&gt;
&lt;p&gt;HK Main is the only host exposed as the public entry layer. It only runs &lt;code&gt;frps&lt;/code&gt;, receives HTTP/HTTPS traffic after Cloudflare, and forwards it through frp to the Mac mini. The less this node does, the better, because it sits on the public side and should have as small an attack surface as possible.&lt;/p&gt;
&lt;p&gt;Proxy or VPN nodes only handle network exit and access duties. They are not involved in Nextcloud, Homepage, Komari, game services, or other main services, and they are not mixed with HK Main. Nodes can come and go without shaking the entry layer or the local service layer.&lt;/p&gt;
&lt;p&gt;This split makes the boundaries easier to reason about: if the entry breaks, check HK Main; if a service breaks, check the Mac mini; if file sync breaks, check Nextcloud; if monitoring looks strange, check Komari; if game access breaks, check &lt;code&gt;gs&lt;/code&gt; and the related ports. I no longer need to dig through a pile of unrelated containers on the same machine.&lt;/p&gt;
&lt;h2 id=&quot;domain-plan&quot;&gt;Domain Plan&lt;/h2&gt;
&lt;p&gt;Domains are also split by responsibility. I no longer want the domain table to describe everything I might run someday; it should describe the entry points that are actually in use right now.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;shishishi3.com&lt;/code&gt; hosts normal web services for myself and trusted visitors:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Subdomain&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;komari.shishishi3.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Komari monitoring panel&lt;/td&gt;
&lt;td&gt;&lt;code&gt;monitor-stack&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;homepage.shishishi3.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Homepage navigation and service entry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;homepage-stack&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vpndl.shishishi3.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;VPN download, config, or instruction entry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vpn-stack&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gs.shishishi3.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Game service entry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;game-stack&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nc.shishishi3.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nextcloud file and sync service&lt;/td&gt;
&lt;td&gt;&lt;code&gt;private-services-stack&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v.shishishi3.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vault / private tools entry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;private-services-stack&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This version is more focused than the April plan: names like &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;panel&lt;/code&gt;, &lt;code&gt;bot&lt;/code&gt;, and &lt;code&gt;demo&lt;/code&gt; are out of the current article instead of pretending to represent the live setup. They can come back later if needed, but a domain plan should reflect running services rather than reserving space for every possibility.&lt;/p&gt;
&lt;p&gt;Infrastructure domains stay quiet and are only used for HK Main, frp, nodes, or internal management entries. Normal service entry points live under &lt;code&gt;shishishi3.com&lt;/code&gt;; infrastructure entry points should not blend into business domains. This makes DNS, certificates, Cloudflare rules, and NPM proxy rules much easier to troubleshoot.&lt;/p&gt;
&lt;h2 id=&quot;public-entry&quot;&gt;Public Entry&lt;/h2&gt;
&lt;p&gt;This setup does not use Cloudflare Tunnel as the main entry. Cloudflare handles DNS, CDN, WAF, and access control, while the real origin entry for local services is centralized on HK Main.&lt;/p&gt;
&lt;p&gt;The web traffic path looks roughly like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;User browser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Cloudflare&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; HK Main:80/443&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; frps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Mac mini frpc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; NPM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -&amp;gt; Komari / Homepage / Nextcloud / Vault / VPN entry containers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TCP forwarding is configured like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HK Main:80  -&amp;gt; frps:8080 -&amp;gt; Mac mini frpc -&amp;gt; NPM:80&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HK Main:443 -&amp;gt; frps:8443 -&amp;gt; Mac mini frpc -&amp;gt; NPM:443&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;frps&lt;/code&gt; only forwards traffic. It does not process business requests on HK Main. TLS transport is enabled between &lt;code&gt;frpc&lt;/code&gt; and &lt;code&gt;frps&lt;/code&gt;; NPM handles domain routing, reverse proxying, and certificate management. Backend services do not expose ports directly to the public internet. They only join Docker networks that NPM can reach.&lt;/p&gt;
&lt;p&gt;Non-HTTP/HTTPS services are not forced through NPM. For example, &lt;code&gt;gs.shishishi3.com&lt;/code&gt; is more like a friendly entry label for game services. The actual connection can depend on the game protocol, port, and audience: trusted friends use Tailscale first, and frp or restricted ports are only fallbacks when installing a client is not practical.&lt;/p&gt;
&lt;h2 id=&quot;stack-layout-on-the-mac-mini&quot;&gt;Stack Layout on the Mac mini&lt;/h2&gt;
&lt;p&gt;Services on the Mac mini are split into a few layers by responsibility. The names do not need to be pretty; the important thing is that the directory tells me what it owns.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;edge-stack&lt;/code&gt; is the entry layer. It contains &lt;code&gt;frpc&lt;/code&gt; and NPM. It brings public traffic into the local machine and forwards each domain to the right service. The NPM admin UI is only reachable from the LAN or Tailscale by default; it is not exposed directly to the public internet.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;monitor-stack&lt;/code&gt; hosts Komari. Monitoring may look like just another panel, but it answers the question “do I need to get up and check something?” That deserves independent persistence and upgrades that are not tied to unrelated services.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;homepage-stack&lt;/code&gt; hosts Homepage. It is an entry point for humans, not for machines: links, groups, service status, and common management addresses can live here. It can be exposed for my own use, but it should not accidentally expose every backend behind it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;private-services-stack&lt;/code&gt; hosts Nextcloud and private services such as &lt;code&gt;v.shishishi3.com&lt;/code&gt;. The common thread is not the tech stack; it is data sensitivity. Files, sync data, vault data, and other private material need explicit volumes, backup plans, and access limits.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vpn-stack&lt;/code&gt; hosts the download, config, or instruction entry behind &lt;code&gt;vpndl.shishishi3.com&lt;/code&gt;. It may not be complex, but it affects access, so it should stay separate from ordinary showcase projects. I do not want a casual page edit to affect someone else’s connection path.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;game-stack&lt;/code&gt; hosts the game services behind &lt;code&gt;gs.shishishi3.com&lt;/code&gt;. Game traffic is not mixed with the web reverse proxy. Access uses Tailscale first, with frp or restricted ports as fallbacks. Saves, mods, and config files are persisted separately and backed up regularly.&lt;/p&gt;
&lt;p&gt;Temporary projects can still have a &lt;code&gt;projects-stack&lt;/code&gt;, but they are no longer the center of this article. The core of the current architecture is keeping Komari, Homepage, the VPN entry, game services, Nextcloud, and private services stable.&lt;/p&gt;
&lt;p&gt;After this split, every stack can be started, stopped, backed up, and migrated independently. When adding a new service, I first decide which layer it belongs to, then add it to that stack instead of throwing every container into the same flat space.&lt;/p&gt;
&lt;h2 id=&quot;security-strategy&quot;&gt;Security Strategy&lt;/h2&gt;
&lt;p&gt;The rule for HK Main is: entry only, no business services. It only opens &lt;code&gt;80&lt;/code&gt;, &lt;code&gt;443&lt;/code&gt;, the &lt;code&gt;frps&lt;/code&gt; control port, and the necessary high forwarding ports. &lt;code&gt;frps&lt;/code&gt; uses a strong token and disables the default admin port. SSH allows key login only and disables password login.&lt;/p&gt;
&lt;p&gt;The rule for the Mac mini is: layered services, centralized data. OrbStack networks are split by stack. Database ports are not mapped to the public side. The NPM admin UI is not exposed directly. &lt;code&gt;.env&lt;/code&gt; files, database passwords, tokens, and key files are never committed to Git. For services such as Nextcloud and Vault, “the page opens” is not enough; the data volumes, backups, and restore path also have to be verified.&lt;/p&gt;
&lt;p&gt;The rule for Cloudflare is: route by domain, put access control in front. Normal web services can use CDN and WAF. Panel-like services should be protected with Cloudflare Access or IP restrictions. Certificates should use DNS validation or Cloudflare Origin Certificates where possible, avoiding HTTP validation problems caused by the frp forwarding chain.&lt;/p&gt;
&lt;p&gt;Backups should cover Cloudflare Pages project configuration, the Astro Blog source repository, Homepage config, Komari data, Nextcloud data and database, Vault-style service volumes, the VPN config entry, game saves, and NPM configuration. A service is not really deployed until it can be restored.&lt;/p&gt;
&lt;h2 id=&quot;maintenance-order&quot;&gt;Maintenance Order&lt;/h2&gt;
&lt;p&gt;This setup is no longer just a migration plan, so maintenance should not start by “launching all containers.” It should start by clarifying entry points and data boundaries.&lt;/p&gt;
&lt;p&gt;The first step is to inventory existing containers, image versions, port mappings, volumes, databases, and config files, then complete one verifiable backup. The second step is to keep stack directories on the Mac mini, with each stack maintaining its own &lt;code&gt;compose.yml&lt;/code&gt;, &lt;code&gt;.env&lt;/code&gt;, and &lt;code&gt;data/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If the entry layer changes, check HK Main’s &lt;code&gt;frps&lt;/code&gt;, firewall, and required ports first, then verify that the Mac mini’s &lt;code&gt;frpc&lt;/code&gt; can connect reliably. Once the entry path works, check &lt;code&gt;edge-stack&lt;/code&gt; and NPM, confirm that &lt;code&gt;npm-http&lt;/code&gt; and &lt;code&gt;npm-https&lt;/code&gt; forwarding are registered, and make sure each domain hits the right Proxy Host.&lt;/p&gt;
&lt;p&gt;If a business service changes, verify it domain by domain: &lt;code&gt;komari&lt;/code&gt; for continuous monitoring data, &lt;code&gt;homepage&lt;/code&gt; for navigation and status cards, &lt;code&gt;vpndl&lt;/code&gt; for usable downloads and config, &lt;code&gt;gs&lt;/code&gt; for game protocol and port access, &lt;code&gt;nc&lt;/code&gt; for file sync and database health, and &lt;code&gt;v&lt;/code&gt; for login, unlock, and persistence.&lt;/p&gt;
&lt;p&gt;Finally, review DNS, NPM reverse proxy rules, backup jobs, and old port mappings. Once the new entry is stable, shut down old containers or old domains instead of saving all the risk for the end.&lt;/p&gt;
&lt;h2 id=&quot;what-comes-next&quot;&gt;What Comes Next&lt;/h2&gt;
&lt;p&gt;This framework has moved from April’s “preparing the rebuild” stage into ongoing maintenance around the current services. A few concrete templates still need to be filled in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;monitor-stack&lt;/code&gt; &lt;code&gt;compose.yml&lt;/code&gt; and Komari backup notes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;homepage-stack&lt;/code&gt; config structure.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;private-services-stack&lt;/code&gt; Nextcloud / Vault backup and restore procedures.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vpn-stack&lt;/code&gt; entry notes and access-control checklist.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;game-stack&lt;/code&gt; &lt;code&gt;compose.yml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;NPM reverse proxy rule list.&lt;/li&gt;
&lt;li&gt;Scheduled backup scripts and restore rehearsal notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once those templates are complete, this will become more than a one-time rebuild note. It will be a reusable way to run my personal servers: new services join by layer, old services migrate by layer, and problems can be traced through entry, service, data, and network boundaries one layer at a time.&lt;/p&gt;
</content:encoded></item><item><title>Raising a Pet in Codex? Wait, Really?</title><link>https://blog.shishishi.cc/en/blog/codex-pet/</link><guid isPermaLink="true">https://blog.shishishi.cc/en/blog/codex-pet/</guid><description>How to enable Codex pets and create a custom pet of your own.</description><pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article walks through the full process of enabling pets in Codex, installing a custom pet, and creating your own pet from a reference image. The example here is &lt;a href=&quot;https://github.com/lliooly/fleetsnowfluff-codex-pet.git&quot;&gt;FleetSnowfluff&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;note&quot; data-callout=&quot;note&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;This is for people who already use Codex Desktop or Codex CLI and want to enable a built-in pet, install a custom animated pet, or make one of their own and share it.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;1-understand-the-basics-first&quot;&gt;1. Understand the Basics First&lt;/h2&gt;
&lt;h3 id=&quot;11-what-is-a-codex-pet&quot;&gt;1.1 What Is a Codex Pet?&lt;/h3&gt;
&lt;p&gt;A Codex pet is a tiny animated companion whose behavior changes with your working state. An installable pet is usually a local folder containing at least:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;lt;pet-id&amp;gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  pet.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  spritesheet.webp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pet.json&lt;/code&gt; describes the name, ID, description, and spritesheet file path. &lt;code&gt;spritesheet.webp&lt;/code&gt; is the animation atlas.&lt;/p&gt;
&lt;p&gt;The final install package in this repo is:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/.codex/pets/fleetsnowfluff/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  pet.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  spritesheet.webp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;12-the-hatch-pet-skill-and-the-pet-package-are-not-the-same-thing&quot;&gt;1.2 The &lt;code&gt;hatch-pet&lt;/code&gt; Skill and the Pet Package Are Not the Same Thing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hatch-pet&lt;/code&gt; is the Codex Skill used to create pets.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.codex/pets/&amp;lt;pet-id&amp;gt;/&lt;/code&gt; is the finished pet package that Codex reads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words, if someone gives you a finished pet package, you do not need to install &lt;code&gt;hatch-pet&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You only need &lt;code&gt;hatch-pet&lt;/code&gt; when you want to generate a pet yourself from an image or prompt.&lt;/p&gt;
&lt;h2 id=&quot;2-how-to-enable-pets-in-codex&quot;&gt;2. How to Enable Pets in Codex&lt;/h2&gt;
&lt;h3 id=&quot;21-enable-a-built-in-pet&quot;&gt;2.1 Enable a Built-in Pet&lt;/h3&gt;
&lt;p&gt;In Codex Desktop:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open Codex.&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;Settings&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;Appearance&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Find the &lt;code&gt;Pets&lt;/code&gt; section.&lt;/li&gt;
&lt;li&gt;Choose the pet you want to enable.&lt;/li&gt;
&lt;li&gt;If the UI shows &lt;code&gt;Tuck Away Pet&lt;/code&gt;, you can use it to temporarily hide the pet.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can also use this inside a Codex session:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/pet&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you cannot see the pet selector, update Codex first, then restart the app.&lt;/p&gt;
&lt;h3 id=&quot;22-enable-a-custom-pet&quot;&gt;2.2 Enable a Custom Pet&lt;/h3&gt;
&lt;p&gt;Put the pet package into the local Codex pets directory.&lt;/p&gt;
&lt;p&gt;macOS / Linux:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/.codex/pets/fleetsnowfluff&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;cp&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; pet.json&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; spritesheet.webp&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/.codex/pets/fleetsnowfluff/&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows PowerShell:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;New-Item&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;ItemType Directory &lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;Force &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$env:USERPROFILE&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;\.codex\pets\fleetsnowfluff&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt;Copy-Item&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt; .\pet.json, .\spritesheet.webp &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$env:USERPROFILE&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;\.codex\pets\fleetsnowfluff\&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then restart Codex and return to:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Settings -&amp;gt; Appearance -&amp;gt; Pets&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Choose &lt;code&gt;FleetSnowfluff&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;3-custom-pet-file-requirements&quot;&gt;3. Custom Pet File Requirements&lt;/h2&gt;
&lt;h3 id=&quot;31-minimal-petjson&quot;&gt;3.1 Minimal &lt;code&gt;pet.json&lt;/code&gt;&lt;/h3&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;fleetsnowfluff&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;displayName&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;FleetSnowfluff&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;description&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;A tiny pink-haired chibi coder sprite with oversized sunglasses, white-blue outfit, and small attached wings.&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;spritesheetPath&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;spritesheet.webp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt; should use lowercase letters, numbers, and hyphens.&lt;/li&gt;
&lt;li&gt;The folder name should preferably match the &lt;code&gt;id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;displayName&lt;/code&gt; is the name shown in the UI and can keep capitalization.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spritesheetPath&lt;/code&gt; usually points to &lt;code&gt;spritesheet.webp&lt;/code&gt; in the same directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;32-spritesheet-atlas-specs&quot;&gt;3.2 Spritesheet Atlas Specs&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;hatch-pet&lt;/code&gt; workflow uses a fixed atlas:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Size: 1536 x 1872&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Grid: 8 columns x 9 rows&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Cell: 192 x 208&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Format: WebP or PNG, with transparent background support required&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The 9 rows map to these states:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Row&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Frames&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right&quot;&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;idle&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
&lt;td&gt;Idle, breathing, blinking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;running-right&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;8&lt;/td&gt;
&lt;td&gt;Moving right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;running-left&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;8&lt;/td&gt;
&lt;td&gt;Moving left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right&quot;&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;waving&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
&lt;td&gt;Waving&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jumping&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;5&lt;/td&gt;
&lt;td&gt;Jumping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right&quot;&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;failed&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;8&lt;/td&gt;
&lt;td&gt;Failure or disappointment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;waiting&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
&lt;td&gt;Waiting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right&quot;&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;running&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
&lt;td&gt;Task in progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right&quot;&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;review&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
&lt;td&gt;Checking or reviewing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Unused cells must be transparent.&lt;/p&gt;
&lt;h2 id=&quot;4-how-to-make-your-own-pet-with-hatch-pet&quot;&gt;4. How to Make Your Own Pet With &lt;code&gt;hatch-pet&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;If you have not installed &lt;code&gt;hatch-pet&lt;/code&gt;, type this in Codex:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$skill-installer hatch-pet&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart Codex after installation.&lt;/p&gt;
&lt;p&gt;If you need to manually install the curated skill, you can refer to the installation method in the OpenAI skills repository:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;python&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;$CODEX_HOME&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/skills/.system/skill-installer/scripts/install-skill-from-github.py&quot;&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  --repo&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; openai/skills&lt;/span&gt;&lt;span style=&quot;color:#56B6C2&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D19A66&quot;&gt;  --path&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; skills/.curated/hatch-pet&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usually, you do not need to run this manually. Using &lt;code&gt;$skill-installer hatch-pet&lt;/code&gt; in Codex is easier.&lt;/p&gt;
&lt;h2 id=&quot;5-create-a-pet-from-a-reference-image&quot;&gt;5. Create a Pet From a Reference Image&lt;/h2&gt;
&lt;h3 id=&quot;51-prepare-the-reference-image&quot;&gt;5.1 Prepare the Reference Image&lt;/h3&gt;
&lt;p&gt;The reference image can be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a character avatar&lt;/li&gt;
&lt;li&gt;a pixel character&lt;/li&gt;
&lt;li&gt;a mascot&lt;/li&gt;
&lt;li&gt;a screenshot&lt;/li&gt;
&lt;li&gt;a logo or sketch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The clearer the image, the more stable the result. It is best to decide these things in advance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the pet name&lt;/li&gt;
&lt;li&gt;the core traits that must be preserved&lt;/li&gt;
&lt;li&gt;any accessories or body structure that must remain&lt;/li&gt;
&lt;li&gt;things that should not be generated&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For FleetSnowfluff, the most important traits in the reference image were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;short pink hair&lt;/li&gt;
&lt;li&gt;oversized black sunglasses&lt;/li&gt;
&lt;li&gt;white and blue futuristic outfit&lt;/li&gt;
&lt;li&gt;small boots&lt;/li&gt;
&lt;li&gt;white and blue wings attached to both sides of the body&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;52-recommended-prompt-pattern&quot;&gt;5.2 Recommended Prompt Pattern&lt;/h3&gt;
&lt;p&gt;You can ask Codex like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Use $hatch-pet to create a Codex pet based on this reference image.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Name: FleetSnowfluff&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Must preserve: short pink hair, black sunglasses, white-blue clothing, white-blue wings.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Style: Codex digital pet, pixel-art-adjacent, small body, thick outline, transparent background.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Avoid: text, background, shadows, floating decorations, music notes, scattered stars.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;When finished, generate pet.json, spritesheet.webp, QA contact sheet, and preview videos.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is to state “must preserve” and “avoid” clearly.&lt;/p&gt;
&lt;h3 id=&quot;53-normal-generation-flow&quot;&gt;5.3 Normal Generation Flow&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;hatch-pet&lt;/code&gt; process can be understood as four steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Prepare the run directory, name, description, and prompt.&lt;/li&gt;
&lt;li&gt;Generate the base pet, which becomes the main visual reference.&lt;/li&gt;
&lt;li&gt;Generate the 9 rows of actions from the base.&lt;/li&gt;
&lt;li&gt;Compose the spritesheet, run QA, and package it into &lt;code&gt;~/.codex/pets/&amp;lt;pet-id&amp;gt;/&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Our FleetSnowfluff run looked like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Getting FleetSnowfluff ready.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Imagining FleetSnowfluff&apos;s main look.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Picturing FleetSnowfluff&apos;s poses.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Hatching FleetSnowfluff.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;54-why-you-should-confirm-the-base-first&quot;&gt;5.4 Why You Should Confirm the Base First&lt;/h3&gt;
&lt;p&gt;The base image is the identity anchor for every later action row. If the base is wrong, the action rows are usually wrong too.&lt;/p&gt;
&lt;p&gt;When checking the base, look for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;whether the character is a complete full-body sprite;&lt;/li&gt;
&lt;li&gt;whether the core traits are preserved;&lt;/li&gt;
&lt;li&gt;whether it has a clean solid chroma-key background;&lt;/li&gt;
&lt;li&gt;whether it contains unwanted text, shadows, floating decorations, and similar artifacts;&lt;/li&gt;
&lt;li&gt;whether it will still read well when scaled down to a 192 x 208 cell.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;FleetSnowfluff’s first base did not include wings, so it had to be regenerated. The second base preserved the wings, and only then did we continue to action-row generation.&lt;/p&gt;
&lt;h2 id=&quot;6-action-row-generation-tips&quot;&gt;6. Action Row Generation Tips&lt;/h2&gt;
&lt;h3 id=&quot;61-use-subagents-to-generate-rows-in-parallel&quot;&gt;6.1 Use Subagents to Generate Rows in Parallel&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;hatch-pet&lt;/code&gt; works well when each action row is handed to a subagent in parallel. Each subagent handles one state:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;idle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;running-right&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;running-left&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;waving&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;jumping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;waiting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;running&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;review&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The parent session only needs to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;record generation results&lt;/li&gt;
&lt;li&gt;decide whether to mirror &lt;code&gt;running-left&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;run the final composition&lt;/li&gt;
&lt;li&gt;check QA&lt;/li&gt;
&lt;li&gt;package the pet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This avoids multiple agents editing &lt;code&gt;imagegen-jobs.json&lt;/code&gt; at the same time and causing conflicts.&lt;/p&gt;
&lt;h3 id=&quot;62-do-not-mirror-running-left-casually&quot;&gt;6.2 Do Not Mirror &lt;code&gt;running-left&lt;/code&gt; Casually&lt;/h3&gt;
&lt;p&gt;Some pets can use a horizontally mirrored &lt;code&gt;running-right&lt;/code&gt; row as &lt;code&gt;running-left&lt;/code&gt;. But if the character has asymmetric traits, do not mirror it.&lt;/p&gt;
&lt;p&gt;FleetSnowfluff has a blue hair clip, sunglasses gradient, and directional wing details, so we generated &lt;code&gt;running-left&lt;/code&gt; separately instead of mirroring it.&lt;/p&gt;
&lt;p&gt;Use this rule of thumb:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If there is a one-sided hair clip, logo, text, or weapon, do not mirror.&lt;/li&gt;
&lt;li&gt;If there is directional lighting or a handheld prop, do not mirror.&lt;/li&gt;
&lt;li&gt;If the character is completely symmetrical, mirroring may be acceptable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;63-common-rules-for-each-row&quot;&gt;6.3 Common Rules for Each Row&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;idle&lt;/code&gt;: use only subtle breathing, blinking, and vertical bobbing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;waving&lt;/code&gt;: express the wave through the hand pose only; do not draw wave lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jumping&lt;/code&gt;: show the jump through body position only; do not draw ground shadows or landing smoke.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;failed&lt;/code&gt;: small tears or smoke close to the body can work, but do not add floating red crosses or symbols.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;waiting&lt;/code&gt;: keep it quiet and simple; do not add complex props.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;running&lt;/code&gt;: this means “task in progress,” not literal running.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;review&lt;/code&gt;: show focus through leaning, head angle, or hand pose; do not add code windows or sheets of paper.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7-qa-and-validation&quot;&gt;7. QA and Validation&lt;/h2&gt;
&lt;p&gt;After generation, check at least these files:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/contact-sheet.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/reports/review.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/reports/validation.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/*.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The QA files in this repo are:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/contact-sheet.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/reports/review.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/reports/validation.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;71-what-to-check-in-the-contact-sheet&quot;&gt;7.1 What to Check in the Contact Sheet&lt;/h3&gt;
&lt;p&gt;Manually inspect the contact sheet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;whether each row represents the correct state;&lt;/li&gt;
&lt;li&gt;whether each state has the correct frame count;&lt;/li&gt;
&lt;li&gt;whether the character identity stays consistent;&lt;/li&gt;
&lt;li&gt;whether wings, hairstyle, sunglasses, and outfit remain consistent;&lt;/li&gt;
&lt;li&gt;whether there are white backgrounds, shadows, floating clutter, cropping issues, or empty frames.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;72-what-to-check-in-json-validation&quot;&gt;7.2 What to Check in JSON Validation&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;validation.json&lt;/code&gt; should have no:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;errors&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: []&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ideally it should also have no:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;warnings&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: []&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;review.json&lt;/code&gt; should show &lt;code&gt;ok: true&lt;/code&gt; for every row.&lt;/p&gt;
&lt;h3 id=&quot;73-preview-videos&quot;&gt;7.3 Preview Videos&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;ffmpeg&lt;/code&gt; is installed, preview MP4 files can be generated for each state.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;ffmpeg&lt;/code&gt; is missing, the spritesheet and pet package can still be generated. You simply will not have video previews. When we created FleetSnowfluff, we hit this first, installed &lt;code&gt;ffmpeg&lt;/code&gt;, and then generated:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/idle.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/running-right.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/running-left.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/waving.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/jumping.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/failed.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/waiting.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/running.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/review.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;8-the-right-way-to-rename-a-pet&quot;&gt;8. The Right Way to Rename a Pet&lt;/h2&gt;
&lt;p&gt;Changing only &lt;code&gt;displayName&lt;/code&gt; is not enough. You should update all of these together:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/.codex/pets/&amp;lt;pet-id&amp;gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pet.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;run metadata&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;README / docs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;QA summary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Git repository directory&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For FleetSnowfluff:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;fleetsnowfluff&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;displayName&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;FleetSnowfluff&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E06C75&quot;&gt;  &quot;spritesheetPath&quot;&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;&quot;spritesheet.webp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The directory should also be:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/.codex/pets/fleetsnowfluff&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you keep the generation run directory, it is also worth replacing the old name globally to avoid confusion during uploads or future maintenance.&lt;/p&gt;
&lt;p&gt;Check for leftover names:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;rg&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;OldName|oldname&quot;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;9-package-it-as-an-uploadable-repository&quot;&gt;9. Package It as an Uploadable Repository&lt;/h2&gt;
&lt;p&gt;Recommended final repository structure:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;my-pet/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  README.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  pet.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  spritesheet.webp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  qa/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    contact-sheet.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    reports/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      review.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      validation.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    videos/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      idle.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      running-right.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      running-left.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      waving.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      jumping.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      failed.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      waiting.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      running.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      review.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Initialize git:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; init&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; commit&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; &quot;Add FleetSnowfluff Codex pet&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Upload to GitHub:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; remote&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; origin&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; git@github.com:&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;r&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;rep&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;o&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; push&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; origin&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you use HTTPS:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; remote&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; origin&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; https://github.com/&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;r&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;rep&lt;/span&gt;&lt;span style=&quot;color:#ABB2BF&quot;&gt;o&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt;.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#61AFEF&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; push&lt;/span&gt;&lt;span style=&quot;color:#D19A66&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; origin&lt;/span&gt;&lt;span style=&quot;color:#98C379&quot;&gt; main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;10-faq&quot;&gt;10. FAQ&lt;/h2&gt;
&lt;h3 id=&quot;the-pet-does-not-appear-in-codex&quot;&gt;The Pet Does Not Appear in Codex&lt;/h3&gt;
&lt;p&gt;Check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;whether it is placed under &lt;code&gt;~/.codex/pets/&amp;lt;pet-id&amp;gt;/&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;whether &lt;code&gt;pet.json&lt;/code&gt; and &lt;code&gt;spritesheet.webp&lt;/code&gt; both exist;&lt;/li&gt;
&lt;li&gt;whether &lt;code&gt;spritesheetPath&lt;/code&gt; in &lt;code&gt;pet.json&lt;/code&gt; is correct;&lt;/li&gt;
&lt;li&gt;whether &lt;code&gt;id&lt;/code&gt; matches the directory name;&lt;/li&gt;
&lt;li&gt;whether Codex has been restarted.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-pet-has-a-white-background-or-background-residue&quot;&gt;The Pet Has a White Background or Background Residue&lt;/h3&gt;
&lt;p&gt;This usually means transparency handling failed. Regenerate or repair the affected row and make sure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the background is a solid chroma-key color;&lt;/li&gt;
&lt;li&gt;there are no shadows, gradients, or glows;&lt;/li&gt;
&lt;li&gt;the background color is not used on the character itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;one-action-row-is-broken&quot;&gt;One Action Row Is Broken&lt;/h3&gt;
&lt;p&gt;Do not remake the whole pet. Repair only the failed row:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;queue repair -&amp;gt; regenerate that row -&amp;gt; record result -&amp;gt; finalize again&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-character-changes-during-animation&quot;&gt;The Character Changes During Animation&lt;/h3&gt;
&lt;p&gt;This is identity drift. When repairing it, use the base image, original image, contact sheet, and failure description as grounding context. Ask it to regenerate only the failed row.&lt;/p&gt;
&lt;h3 id=&quot;i-want-to-preserve-a-specific-detail&quot;&gt;I Want to Preserve a Specific Detail&lt;/h3&gt;
&lt;p&gt;Say so before generating the base. For example, FleetSnowfluff’s wings were required:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Must preserve the white-blue wings. The wings are part of the body silhouette, not floating decorations.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;11-a-copy-ready-pet-creation-request&quot;&gt;11. A Copy-ready Pet Creation Request&lt;/h2&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Use $hatch-pet to help me create a Codex pet.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Name: &amp;lt;YourPetName&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reference image: attached&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Must preserve:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- &amp;lt;core trait 1&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- &amp;lt;core trait 2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- &amp;lt;core trait 3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Style:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Codex digital pet&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- pixel-art-adjacent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- small chibi proportions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- thick dark outline&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- transparent background&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- flat cel shading&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Avoid:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- background&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- shadows&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- floating decorations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- wave lines&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- speed lines&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- particle effects&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please generate:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- pet.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- spritesheet.webp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- QA contact sheet&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- preview videos for every state&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;If one step fails, retry only the failed row instead of remaking the whole pet.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;When finished, organize it as a git repository so it can be uploaded easily.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;12-fleetsnowfluff-output-from-this-repo&quot;&gt;12. FleetSnowfluff Output From This Repo&lt;/h2&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pet.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;spritesheet.webp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/contact-sheet.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/reports/review.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/reports/validation.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;qa/videos/*.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example install path:&lt;/p&gt;
&lt;pre class=&quot;astro-code one-dark-pro&quot; style=&quot;background-color:#282c34;color:#abb2bf; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/.codex/pets/fleetsnowfluff&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This repository can be uploaded directly, or copied directly to &lt;code&gt;~/.codex/pets/fleetsnowfluff&lt;/code&gt; for use.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.openai.com/codex/cloud&quot;&gt;OpenAI Codex docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openai.com/codex/&quot;&gt;OpenAI Codex product page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.openai.com/codex/skills&quot;&gt;OpenAI Agent Skills docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/skills&quot;&gt;openai/skills repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/skills/tree/main/skills/.curated/hatch-pet&quot;&gt;openai/skills curated hatch-pet directory&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>How to Create an Apple ID for Another Region on the Web</title><link>https://blog.shishishi.cc/en/blog/foreign-apple-id/</link><guid isPermaLink="true">https://blog.shishishi.cc/en/blog/foreign-apple-id/</guid><description>A web-based guide to creating an Apple Account for another region, covering preparation, registration, verification, region switching, and common issues.</description><pubDate>Wed, 06 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div class=&quot;note&quot; data-callout=&quot;note&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;When creating or using an account, follow Apple’s terms of service and the laws and regulations that apply to you. I do not recommend buying, sharing, or using accounts from unclear sources.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;when-this-is-useful&quot;&gt;When This Is Useful&lt;/h2&gt;
&lt;p&gt;This guide is useful if you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;want to download apps available in the U.S. App Store or another regional App Store;&lt;/li&gt;
&lt;li&gt;need to try Apple services that are available in the U.S. or another region;&lt;/li&gt;
&lt;li&gt;want to keep your main account separate from a regional App Store account, reducing the impact on your primary iCloud account.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is not recommended for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bypassing copyright, payment, subscription, or local legal restrictions;&lt;/li&gt;
&lt;li&gt;using a fake identity, fake payment method, or billing details from an unverified source;&lt;/li&gt;
&lt;li&gt;creating or buying ready-made third-party Apple IDs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-to-prepare&quot;&gt;What to Prepare&lt;/h2&gt;
&lt;p&gt;Before you start, prepare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an email address that has never been used for an Apple ID;&lt;/li&gt;
&lt;li&gt;a phone number that can receive verification codes. A foreign phone number is preferred when you have one, but a &lt;code&gt;+86&lt;/code&gt; number can also work. The same phone number can be used to register multiple accounts;&lt;/li&gt;
&lt;li&gt;a computer, phone, or tablet that can access Apple’s website;&lt;/li&gt;
&lt;li&gt;a stable network connection. A proxy is usually not required;&lt;/li&gt;
&lt;li&gt;if you plan to buy paid content later, a legitimate payment method that matches the &lt;code&gt;U.S. or target region&lt;/code&gt;. For users in mainland China, gift cards are often the easier option.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When creating an Apple Account on the web, the important parts are the email address, password, country or region, birthday, and phone verification. Payment information is usually not the core step during web registration. You may only be asked to add it later when signing in to services such as the App Store, Apple Music, Apple TV, or iCloud+.&lt;/p&gt;
&lt;h2 id=&quot;registration-steps&quot;&gt;Registration Steps&lt;/h2&gt;
&lt;h3 id=&quot;1-open-the-apple-account-sign-up-page&quot;&gt;1. Open the Apple Account Sign-up Page&lt;/h3&gt;
&lt;p&gt;Open this page in your browser:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://account.apple.com/account&quot;&gt;https://account.apple.com/account&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I recommend using an incognito window or a fresh browser profile so that an existing signed-in account does not affect the registration page.&lt;/p&gt;
&lt;figure style=&quot;text-align: center;&quot; data-lightbox=&quot;article&quot;&gt;&lt;img alt=&quot;Apple Account sign-up page&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;514&quot; height=&quot;1262&quot; src=&quot;https://blog.shishishi.cc/_astro/account.DyZvxxat_Z1Qf6FP.webp&quot; srcset=&quot;&quot;&gt;&lt;/figure&gt;
&lt;h3 id=&quot;2-fill-in-the-account-information&quot;&gt;2. Fill in the Account Information&lt;/h3&gt;
&lt;p&gt;Fill in the form as prompted:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Suggestion&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;Any name you can consistently remember&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Country or region&lt;/td&gt;
&lt;td&gt;Choose the &lt;code&gt;[U.S. or target region]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date of birth&lt;/td&gt;
&lt;td&gt;Any date that makes the account holder over 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;Use an email address that has &lt;strong&gt;not&lt;/strong&gt; been used for an Apple ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password&lt;/td&gt;
&lt;td&gt;Use a strong password, or one you know well, and store it safely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phone number&lt;/td&gt;
&lt;td&gt;Use a number that can receive verification codes normally&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The country or region is the key part of this step. The region you choose affects the content you see in Apple services, available payment methods, billing information, and some age-verification requirements.&lt;/p&gt;
&lt;h3 id=&quot;3-choose-whether-to-receive-apple-updates&quot;&gt;3. Choose Whether to Receive Apple Updates&lt;/h3&gt;
&lt;p&gt;The page may offer email subscriptions for Apple updates, news, software, products, and services.&lt;/p&gt;
&lt;p&gt;This step is optional. If you want fewer emails, uncheck the boxes. If you want official Apple updates, leave them selected.&lt;/p&gt;
&lt;figure style=&quot;text-align: center;&quot; data-lightbox=&quot;article&quot;&gt;&lt;img alt=&quot;Email subscription options for Apple products and services&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;764&quot; height=&quot;310&quot; src=&quot;https://blog.shishishi.cc/_astro/Announcements.BReIIh4h_ZtRJdG.webp&quot; srcset=&quot;&quot;&gt;&lt;/figure&gt;
&lt;h3 id=&quot;4-complete-verification&quot;&gt;4. Complete Verification&lt;/h3&gt;
&lt;p&gt;After you click continue, Apple usually asks you to verify both your email address and phone number.&lt;/p&gt;
&lt;p&gt;The flow is usually:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open your email inbox and find the verification code from Apple.&lt;/li&gt;
&lt;li&gt;Enter the email code on the web page.&lt;/li&gt;
&lt;li&gt;Receive the SMS verification code.&lt;/li&gt;
&lt;li&gt;Enter the phone code on the web page.&lt;/li&gt;
&lt;li&gt;Wait for the account to be created.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If verification codes do not arrive, check spam, SMS filtering, phone number format, browser proxy settings, and network status. Avoid repeatedly submitting the form in a short period, as that may trigger extra restrictions.&lt;/p&gt;
&lt;h3 id=&quot;5-sign-in-to-apple-account-on-the-web&quot;&gt;5. Sign in to Apple Account on the Web&lt;/h3&gt;
&lt;p&gt;After registration, you will usually be signed in to the Apple Account page automatically.&lt;/p&gt;
&lt;p&gt;After signing in, check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;whether the name and birthday are correct;&lt;/li&gt;
&lt;li&gt;whether the country or region is the &lt;code&gt;U.S. or target region&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;whether the email address and phone number are verified;&lt;/li&gt;
&lt;li&gt;whether account security settings are complete;&lt;/li&gt;
&lt;li&gt;whether two-factor authentication is already enabled or waiting to be enabled later.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure style=&quot;text-align: center;&quot; data-lightbox=&quot;article&quot;&gt;&lt;img alt=&quot;Apple Account page&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2126&quot; height=&quot;1210&quot; src=&quot;https://blog.shishishi.cc/_astro/AAccount.DbBuNs83_ZCA4K.webp&quot; srcset=&quot;&quot;&gt;&lt;/figure&gt;
&lt;h3 id=&quot;6-add-payment-and-billing-information-later&quot;&gt;6. Add Payment and Billing Information Later&lt;/h3&gt;
&lt;p&gt;Because binding a foreign bank card can be difficult, I recommend choosing &lt;code&gt;None&lt;/code&gt; when it is available, then using gift cards instead.&lt;/p&gt;
&lt;h3 id=&quot;7-sign-in-to-the-app-store&quot;&gt;7. Sign in to the App Store&lt;/h3&gt;
&lt;div class=&quot;warning&quot; data-callout=&quot;warning&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Important&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;Although the web account page may still show the selected country or region, the account can be sent back to your original local region when you sign in to the App Store. So the next step is to switch the App Store region first.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This method uses regional redirect links.&lt;/p&gt;
&lt;p&gt;First, sign out of your App Store account.&lt;/p&gt;
&lt;p&gt;iOS 26.4 and later: open the App Store, tap the Apple account avatar in the top-right corner, tap the account at the top, verify if prompted, scroll to the bottom, and tap &lt;code&gt;Sign Out&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;iOS 26.3 and earlier: open the App Store, tap the Apple account avatar in the top-right corner, scroll to the bottom, and tap &lt;code&gt;Sign Out&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;macOS: open the App Store, click &lt;code&gt;Store&lt;/code&gt; in the menu bar, then sign out.&lt;/p&gt;
&lt;div class=&quot;important&quot; data-callout=&quot;important&quot; data-collapsible=&quot;false&quot;&gt;&lt;div class=&quot;admonition-title&quot;&gt;&lt;div class=&quot;callout-title-text&quot;&gt;Important&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;callout-content&quot;&gt;&lt;p&gt;After signing out, do not sign in immediately. Otherwise, the account may be sent back to its original region.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Next, copy one of the links below into Safari and press Enter.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Quick redirect link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;United States&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143441&amp;amp;cc=us&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Japan&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143462&amp;amp;cc=jp&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;South Korea&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143466&amp;amp;cc=kr&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hong Kong&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143463&amp;amp;cc=hk&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nigeria&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143561&amp;amp;cc=ng&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turkey&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143480&amp;amp;cc=tr&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egypt&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143516&amp;amp;cc=eg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;India&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143467&amp;amp;cc=in&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Argentina&lt;/td&gt;
&lt;td&gt;&lt;code&gt;itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143505&amp;amp;cc=ar&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It is normal if App Store says it cannot connect after you open the link.&lt;/p&gt;
&lt;figure style=&quot;text-align: center;&quot; data-lightbox=&quot;article&quot;&gt;&lt;img alt=&quot;Cannot connect to App Store&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;520&quot; height=&quot;352&quot; src=&quot;https://blog.shishishi.cc/_astro/error.ByhhROjw_1PkwVH.webp&quot; srcset=&quot;&quot;&gt;&lt;/figure&gt;
&lt;h3 id=&quot;8-check-the-account&quot;&gt;8. Check the Account&lt;/h3&gt;
&lt;p&gt;Finally, sign in and check whether the country or region is correct. Download a few apps to help lock the region in place, or redeem a gift card directly.&lt;/p&gt;
&lt;p&gt;One extra note: because Apple’s region-changing policy requires the account to have no remaining balance, redeeming a gift card can be a relatively stable way to lock the region.&lt;/p&gt;
&lt;h2 id=&quot;usage-tips&quot;&gt;Usage Tips&lt;/h2&gt;
&lt;p&gt;I recommend using a regional Apple ID only for App Store downloads, app updates, and necessary regional services. It should not replace your main iCloud account.&lt;/p&gt;
&lt;p&gt;A practical daily setup is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;keep your main account signed in to iCloud;&lt;/li&gt;
&lt;li&gt;switch to the regional account in the App Store when needed;&lt;/li&gt;
&lt;li&gt;after downloading or updating apps, switch back to your main account.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This reduces the chance of affecting core data such as photos, backups, contacts, and Find My devices.&lt;/p&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;/h2&gt;
&lt;h3 id=&quot;why-is-there-no-none-payment-option&quot;&gt;Why Is There No &lt;code&gt;None&lt;/code&gt; Payment Option?&lt;/h3&gt;
&lt;p&gt;Possible reasons include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the current service or region does not support skipping payment details;&lt;/li&gt;
&lt;li&gt;Apple requires billing information when the account first uses media services;&lt;/li&gt;
&lt;li&gt;the payment method, billing address, and account region do not match;&lt;/li&gt;
&lt;li&gt;the device, network, or account status triggered extra verification.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Web registration does not always ask for a payment method immediately. Whether &lt;code&gt;None&lt;/code&gt; is available later when using Apple media or paid services depends on what Apple’s page shows at that moment.&lt;/p&gt;
&lt;h3 id=&quot;can-a-regional-apple-id-use-a-mainland-china-bank-card&quot;&gt;Can a Regional Apple ID Use a Mainland China Bank Card?&lt;/h3&gt;
&lt;p&gt;Not always. Supported payment methods vary by country or region. In most cases, the payment method needs to match the account’s country or region. Check Apple’s official payment-method support list for details.&lt;/p&gt;
&lt;h3 id=&quot;can-i-use-it-together-with-my-main-account-long-term&quot;&gt;Can I Use It Together With My Main Account Long Term?&lt;/h3&gt;
&lt;p&gt;Yes, but I recommend switching only at the App Store level. Avoid frequently switching your main iCloud account.&lt;/p&gt;
&lt;p&gt;Also note that apps are usually tied to the account that downloaded them. If an app was downloaded with the regional account, future updates may require signing in with that same regional account again.&lt;/p&gt;
&lt;h3 id=&quot;what-if-the-account-gets-locked&quot;&gt;What If the Account Gets Locked?&lt;/h3&gt;
&lt;p&gt;Use Apple’s official account recovery page first:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://iforgot.apple.com/&quot;&gt;https://iforgot.apple.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Do not keep guessing the password, and do not use third-party unlocking services.&lt;/p&gt;
&lt;h3 id=&quot;if-i-already-have-an-apple-id-can-i-just-change-its-region&quot;&gt;If I Already Have an Apple ID, Can I Just Change Its Region?&lt;/h3&gt;
&lt;p&gt;Yes, you can change the country or region in account settings. Before doing so, however, you usually need to handle subscriptions, remaining balance, payment methods, Family Sharing, and related issues. Changing the region of your main account has a larger impact, so I prefer using a separate account for regional App Store needs.&lt;/p&gt;
&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Enable two-factor authentication.&lt;/li&gt;
&lt;li&gt;Keep the email address, phone number, and recovery methods safe.&lt;/li&gt;
&lt;li&gt;Do not lend the account to other people.&lt;/li&gt;
&lt;li&gt;Do not buy ready-made accounts.&lt;/li&gt;
&lt;li&gt;Do not sign in on untrusted devices.&lt;/li&gt;
&lt;li&gt;For payments, subscriptions, and Family Sharing, follow the prompts shown on Apple’s official pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;about-gift-cards&quot;&gt;About Gift Cards&lt;/h2&gt;
&lt;p&gt;Gift cards from Xianyu are often the most convenient, but they may come with risks such as black-card issues or slightly worse exchange rates.&lt;/p&gt;
&lt;p&gt;For U.S. accounts, you can refer to this article:
&lt;a href=&quot;https://zhuanlan.zhihu.com/p/636121931&quot;&gt;A simple, safe, and reliable guide to buying and redeeming U.S. Apple ID gift cards - Zhihu, Apple Dabai&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For Turkey accounts, I recommend going directly to &lt;a href=&quot;https://www.seagm.com/&quot;&gt;SEAGM&lt;/a&gt; or &lt;a href=&quot;https://www.oyunfor.com/&quot;&gt;Oyunfor&lt;/a&gt;. SEAGM supports Alipay, while Oyunfor only supports cards such as MasterCard and Visa.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://support.apple.com/en-us/ht204034&quot;&gt;How to create a new Apple Account - Apple Support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.apple.com/kb/HT201266&quot;&gt;Add a payment method to your Apple Account - Apple Support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.apple.com/en-ca/111741&quot;&gt;Payment methods that you can use with your Apple Account - Apple Support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linux.do/t/topic/2021625?mobile_view=0&quot;&gt;Foreign-region Apple ID registration guide using Turkey as an example - Linuxdo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/636121931&quot;&gt;A simple, safe, and reliable guide to buying and redeeming U.S. Apple ID gift cards - Zhihu, Apple Dabai&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>What Stays With Us Finds Its Way Back</title><link>https://blog.shishishi.cc/en/blog/memory/</link><guid isPermaLink="true">https://blog.shishishi.cc/en/blog/memory/</guid><description>In memory of what has faded away...</description><pubDate>Wed, 10 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What’s past is prologue.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A life keeps moving forward. Again and again, we begin somewhere new. And little by little, we start to forget so many people, so many things…&lt;/p&gt;
&lt;p&gt;Some people say that is just how life is. We meet so many people and go through so much in one lifetime. How could anyone remember all of it?&lt;/p&gt;
&lt;p&gt;Right. How could anyone remember all of it…&lt;/p&gt;
&lt;p&gt;This is where I will write down the memories I am slowly losing. When I come back and see these words again, I hope they can still lead me back to what I have forgotten…&lt;/p&gt;
</content:encoded></item></channel></rss>