Files
blog/posts/bb18d851/index.html
T
biss f839e19947 更新包
-- [CST 2026-08-04 Tuesday 16:05:05]
2026-08-04 16:05:05 +08:00

1277 lines
113 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html><html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。"><meta name="author" content="biss"><meta name="keywords" content="技术, 1panel"><link rel="canonical" href="https://blog.biss.click/posts/bb18d851/"><link rel="alternate" type="application/rss+xml" title="Bi's Blog RSS" href="https://blog.biss.click/rss.xml"><meta property="og:site_name" content="Bi's Blog"><meta property="og:locale" content="zh_CN"><meta property="og:type" content="article"><meta property="og:title" content="为1Panel添加自己想要的应用 | Bi's Blog"><meta property="og:description" content="这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。"><meta property="og:url" content="https://blog.biss.click/posts/bb18d851/"><meta property="og:image" content="https://pic.biss.click/image/fa0178dd-b1f6-48eb-8690-42faf26c9f52.webp"><meta property="og:image:alt" content="为1Panel添加自己想要的应用"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="为1Panel添加自己想要的应用 | Bi's Blog"><meta name="twitter:description" content="这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。"><meta name="twitter:image" content="https://pic.biss.click/image/fa0178dd-b1f6-48eb-8690-42faf26c9f52.webp"><meta property="article:published_time" content="2025-08-19T10:43:16.000Z"><meta property="article:modified_time" content="2025-08-19T10:43:16.000Z"><meta property="article:author" content="biss"><meta property="article:tag" content="技术"><meta property="article:tag" content="1panel"><script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"为1Panel添加自己想要的应用","description":"这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。","image":["https://pic.biss.click/image/fa0178dd-b1f6-48eb-8690-42faf26c9f52.webp"],"datePublished":"2025-08-19T10:43:16.000Z","dateModified":"2025-08-19T10:43:16.000Z","wordCount":1497,"inLanguage":"zh-CN","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.biss.click/posts/bb18d851/"},"author":{"@type":"Person","name":"biss","url":"https://github.com/bishshi"},"publisher":{"@type":"Person","name":"biss","image":"https://free.picui.cn/free/2025/08/10/689845496a283.png"},"keywords":"技术, 1panel","articleSection":"技术"}</script><link rel="icon" href="/images/Bi.ico"><link rel="manifest" href="/manifest.webmanifest"><link rel="apple-touch-icon" href="/images/apple-touch-icon.png"><meta name="theme-color" content="#f7f5ef" media="(prefers-color-scheme: light)"><meta name="theme-color" content="#0b1014" media="(prefers-color-scheme: dark)"><script defer src="https://umami.biss.click/script.js" data-website-id="3d97b310-b241-4fc6-bc9c-68e317fc7d42"></script><script>window.SITE_CONFIG = {"site":{"url":"https://blog.biss.click","name":"Bi's Blog","description":"Personal blog by biss.","language":"zh-CN","domain":"blog.biss.click","slogan":"好奇心转化为代码的空间。","startTime":"2025-01-01T00:00:00+08:00","copyrightStartYear":2025,"license":"内容采用 CC BY-NC-SA 4.0 协议"},"author":{"name":"biss","email":"bishsh2006@gmail.com","github":"https://github.com/bishshi","avatar":"https://free.picui.cn/free/2025/08/10/689845496a283.png"},"comments":{"envId":"https://comment.biss.click","region":"","script":"https://cdn.jsdelivr.net/npm/twikoo@1.7.7/dist/twikoo.min.js"},"analytics":{"umami":{"script":"https://umami.biss.click/script.js","websiteId":"3d97b310-b241-4fc6-bc9c-68e317fc7d42","statsEndpoint":"https://blogumami.biss.click"}},"talks":{"apiUrl":"https://mm.biss.click/api/echo/page","pageSize":30,"homeLimit":6,"cacheKey":"talksCache","cacheTimeKey":"talksCacheTime","cacheDurationMs":1800000,"fallbackAvatar":"https://free.picui.cn/free/2025/08/10/689845496a283.png"},"search":{"apiKey":"2uxQzk7eD2GBCljdSmIiuU3ause7UK9n","server":{"host":"typesense.biss.click","port":"443","protocol":"https"},"indexName":"blogs","searchParams":{"query_by":"title,content","highlight_full_fields":"title,content","per_page":8,"num_typos":1,"typo_tokens_threshold":1,"prefix":true},"ui":{"maxRetries":10,"retryDelay":100,"animationDuration":300,"maxInitRetries":30,"placeholder":"输入关键词寻找故事..."}},"externalLinks":{"waitSeconds":10,"whitelist":["blog.biss.click","biss.click","github.com","localhost","127.0.0.1"]}};</script><script>
(() => {
const storageKey = 'site-theme';
const media = window.matchMedia('(prefers-color-scheme: dark)');
const getSystemTheme = () => (media.matches ? 'dark' : 'light');
const getSavedTheme = () => {
try {
const theme = localStorage.getItem(storageKey);
return theme === 'dark' || theme === 'light' ? theme : null;
} catch {
return null;
}
};
document.documentElement.dataset.theme = getSavedTheme() || getSystemTheme();
})();
</script><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/instantsearch.css/themes/reset-min.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/@fortawesome/fontawesome-free@6.7.2/css/all.min.css"><title>为1Panel添加自己想要的应用 | Bi&#39;s Blog</title><link rel="stylesheet" href="/_astro/BaseLayout.DvPRLM9I.css"></head> <body> <header class="site-header"> <nav class="nav"> <a class="brand" href="/" aria-label="Bi's Blog 首页"><span>biss</span><i>.log</i></a> <button class="nav-menu-toggle" type="button" aria-label="打开菜单" aria-expanded="false" data-nav-toggle> <i class="fa-solid fa-bars nav-menu-open" aria-hidden="true"></i> <i class="fa-solid fa-xmark nav-menu-close" aria-hidden="true"></i> </button> <div class="nav-drawer-backdrop" data-nav-backdrop></div> <div class="nav-menu" data-nav-menu> <div class="nav-links nav-center"> <a href="/archives/"><i class="fa-solid fa-box-archive nav-icon" aria-hidden="true"></i><span>归档</span></a><a href="/categories/"><i class="fa-solid fa-folder-open nav-icon" aria-hidden="true"></i><span>分类</span></a><a href="/tags/"><i class="fa-solid fa-tags nav-icon" aria-hidden="true"></i><span>标签</span></a><a href="/shuoshuo/"><i class="fa-regular fa-comments nav-icon" aria-hidden="true"></i><span>说说</span></a><a href="/about/"><i class="fa-solid fa-heart nav-icon" aria-hidden="true"></i><span>关于</span></a><a href="/link/"><i class="fa-solid fa-link nav-icon" aria-hidden="true"></i><span>友链</span></a> </div> <div class="nav-links nav-actions"> <button class="nav-random" type="button" onclick="randomPost()" aria-label="前往随机文章" title="前往随机文章"> <i class="fa-solid fa-shuffle nav-icon" aria-hidden="true"></i> <span>随机文章</span> </button> <button class="nav-search search-typesense-trigger" type="button" aria-label="搜索" title="搜索"> <i class="fa-solid fa-magnifying-glass nav-icon" aria-hidden="true"></i> <span>搜索</span> </button> <button class="nav-console" type="button" aria-label="打开中控台" title="中控台" data-console-open> <i class="fa-solid fa-gauge-high nav-icon" aria-hidden="true"></i> <span>中控台</span> </button> <button class="theme-toggle" type="button" aria-label="切换夜间模式" title="切换夜间模式" data-theme-toggle> <i class="fa-solid fa-sun theme-toggle-sun nav-icon" aria-hidden="true"></i> <i class="fa-solid fa-moon theme-toggle-moon nav-icon" aria-hidden="true"></i> </button> </div> </div> </nav> </header> <div class="control-console" data-console-modal aria-hidden="true"> <div class="control-console-backdrop" data-console-close></div> <section class="control-console-panel" role="dialog" aria-modal="true" aria-labelledby="control-console-title" tabindex="-1"> <header class="control-console-head"> <div> <p>CONTROL CENTER</p> <h2 id="control-console-title">中控台</h2> </div> <button class="control-console-close" type="button" aria-label="关闭中控台" title="关闭" data-console-close> <i class="fa-solid fa-xmark" aria-hidden="true"></i> </button> </header> <div class="control-console-grid"> <a class="control-site-card" href="https://biss.click" target="_blank" rel="noreferrer"> <i class="fa-solid fa-house-chimney" aria-hidden="true"></i> <span> <strong>主站入口</strong> <em>个人主页与常用入口集合</em> </span> <small>biss.click</small> </a><a class="control-site-card" href="https://mm.biss.click" target="_blank" rel="noreferrer"> <i class="fa-regular fa-comments" aria-hidden="true"></i> <span> <strong>说说动态</strong> <em>短句、碎片记录和近况更新</em> </span> <small>mm.biss.click</small> </a><a class="control-site-card" href="https://comment.biss.click" target="_blank" rel="noreferrer"> <i class="fa-solid fa-message" aria-hidden="true"></i> <span> <strong>评论系统</strong> <em>Twikoo 评论服务与访客互动</em> </span> <small>comment.biss.click</small> </a><a class="control-site-card" href="https://umami.biss.click" target="_blank" rel="noreferrer"> <i class="fa-solid fa-chart-line" aria-hidden="true"></i> <span> <strong>站点统计</strong> <em>Umami 访问统计与站点数据</em> </span> <small>umami.biss.click</small> </a> </div> <div class="control-console-info" aria-label="站点信息"> <section> <span>当前站点</span> <strong>blog.biss.click</strong> </section> <section> <span>站点作者</span> <strong>biss</strong> </section> <section> <span>运行起点</span> <strong>2025-01-01</strong> </section> </div> </section> </div> <main> <div class="reading-progress" aria-hidden="true"><span data-reading-progress></span></div> <aside class="reading-resume" data-reading-resume hidden aria-live="polite"> <div class="reading-resume-icon" aria-hidden="true"><i class="fa-solid fa-book-open"></i></div> <div> <strong>继续上次阅读?</strong> <p>你上次读到了 <span data-reading-resume-percent>--</span></p> </div> <div class="reading-resume-actions"> <button type="button" data-reading-continue>继续阅读</button> <button type="button" data-reading-restart>从头开始</button> </div> </aside> <section class="post-layout"> <article class="article post-article"> <header class="article-header article-header-cover"> <img class="article-header-cover-img" src="https://pic.biss.click/image/fa0178dd-b1f6-48eb-8690-42faf26c9f52.webp" alt="" loading="eager" decoding="async"> <div class="article-header-content"> <div class="article-date-line"> <time datetime="2025-08-19T10:43:16.000Z">发布于 2025/08/19</time> </div> <h1>为1Panel添加自己想要的应用</h1> <div class="article-hero-meta" aria-label="文章信息"> <span>发布于 2025/08/19</span> <a href="/categories/tech/">技术</a> <span>总字数:1.5k</span> <span>阅读时长:4 分钟</span> </div> </div> </header> <section class="article-summary-card" aria-label="文章导读"> <div class="article-summary-header"> <span class="article-summary-brand"> <span class="article-summary-icon" aria-hidden="true"></span>
文章导读
</span> <span class="article-summary-model">摘要由 Qwen 生成</span> </div> <p class="article-summary-content"> <span class="article-summary-text">这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。</span> </p> </section> <aside class="butterfly-note butterfly-note--warning article-outdated-note" aria-label="文章时效性提示" data-freshness-at="2025-08-19T10:43:16.000Z" data-has-new-version="false" hidden> <i class="fa-solid fa-triangle-exclamation" aria-hidden="true"></i> <div class="butterfly-note__content"> <p>
注意:本文最后维护于 2025/08/19,距今已超过一年,部分内容可能已经过时,请结合最新资料判断。
</p> </div> </aside> <div class="prose" data-toc="[{&quot;depth&quot;:1,&quot;text&quot;:&quot;预准备&quot;,&quot;id&quot;:&quot;预准备&quot;},{&quot;depth&quot;:1,&quot;text&quot;:&quot;创建文件&quot;,&quot;id&quot;:&quot;创建文件&quot;},{&quot;depth&quot;:2,&quot;text&quot;:&quot;文件目录&quot;,&quot;id&quot;:&quot;文件目录&quot;},{&quot;depth&quot;:2,&quot;text&quot;:&quot;应用声明文件`data.yml`&quot;,&quot;id&quot;:&quot;应用声明文件datayml&quot;},{&quot;depth&quot;:2,&quot;text&quot;:&quot;应用标签 - tags 字段&quot;,&quot;id&quot;:&quot;应用标签-tags-字段&quot;},{&quot;depth&quot;:1,&quot;text&quot;:&quot;应用类型 - type 字段&quot;,&quot;id&quot;:&quot;应用类型-type-字段&quot;},{&quot;depth&quot;:1,&quot;text&quot;:&quot;应用参数配置文件 data.yml&quot;,&quot;id&quot;:&quot;应用参数配置文件-datayml&quot;},{&quot;depth&quot;:2,&quot;text&quot;:&quot;关于端口字段:&quot;,&quot;id&quot;:&quot;关于端口字段&quot;},{&quot;depth&quot;:2,&quot;text&quot;:&quot;关于 type 字段:&quot;,&quot;id&quot;:&quot;关于-type-字段&quot;},{&quot;depth&quot;:2,&quot;text&quot;:&quot;脚本&quot;,&quot;id&quot;:&quot;脚本&quot;},{&quot;depth&quot;:1,&quot;text&quot;:&quot;本地测试&quot;,&quot;id&quot;:&quot;本地测试&quot;},{&quot;depth&quot;:1,&quot;text&quot;:&quot;提交文件&quot;,&quot;id&quot;:&quot;提交文件&quot;},{&quot;depth&quot;:1,&quot;text&quot;:&quot;提交 Pull Request&quot;,&quot;id&quot;:&quot;提交-pull-request&quot;}]"> <p>记录一下自己为1Panel贡献自己应用的经历</p>
<h1 id="预准备">预准备</h1>
<ol>
<li>
Fork仓库
<a class="hexo-link-card" href="https://github.com/1Panel-dev/appstore" target="_blank" rel="external nofollow noopener noreferrer">
<span class="hexo-link-card__icon" aria-hidden="true">1</span>
<span class="hexo-link-card__body">
<strong>1Panel appstore 仓库</strong>
<span>1panel appstore · github.com</span>
</span>
</a>
把仓库fork到自己的仓库
</li>
<li>pull
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> clone</span><span style="color:#79B8FF"> -b</span><span style="color:#9ECBFF"> dev</span><span style="color:#9ECBFF"> https://</span><span style="color:#F97583">&#x3C;</span><span style="color:#9ECBFF">your-github-usernam</span><span style="color:#E1E4E8">e</span><span style="color:#F97583">></span><span style="color:#9ECBFF">/appstore</span></span></code></pre>
创建新分支
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#79B8FF">cd</span><span style="color:#9ECBFF"> appstore</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> checkout</span><span style="color:#79B8FF"> -b</span><span style="color:#9ECBFF"> app/</span><span style="color:#F97583">&#x3C;</span><span style="color:#9ECBFF">app-nam</span><span style="color:#E1E4E8">e</span><span style="color:#F97583">></span></span></code></pre>
</li>
</ol>
<h1 id="创建文件">创建文件</h1>
<h2 id="文件目录">文件目录</h2>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="plaintext"><code><span class="line"><span>├──halo // 以 halo 的 key 命名 ,下面解释什么是 key </span></span>
<span class="line"><span> ├── logo.png // 应用 logo , 最好是 180 * 180 px 不要超过 10 KB </span></span>
<span class="line"><span> ├── data.yml // 应用声明文件</span></span>
<span class="line"><span> ├── README.md // 应用的 README</span></span>
<span class="line"><span> ├── 2.2.0 // 应用版本 注意不要以 v 开头</span></span>
<span class="line"><span> │   ├── data.yml // 应用的参数配置,下面有详细介绍</span></span>
<span class="line"><span> │   ├── data // 挂载出来的目录 </span></span>
<span class="line"><span> | ├── scripts // 脚本目录 存放 init.sh upgrade.sh uninstall.sh</span></span>
<span class="line"><span> │   └── docker-compose.yml // docker-compose 文件</span></span>
<span class="line"><span> └── 2.3.2</span></span>
<span class="line"><span> ├── data.yml</span></span>
<span class="line"><span> ├── data</span></span>
<span class="line"><span> └── docker-compose.yml</span></span></code></pre>
<h2 id="应用声明文件datayml">应用声明文件<code>data.yml</code></h2>
<p>本文件主要用于声明应用的一些信息</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="yml"><code><span class="line"><span style="color:#85E89D">additionalProperties</span><span style="color:#E1E4E8">: </span><span style="color:#6A737D">#固定参数</span></span>
<span class="line"><span style="color:#85E89D"> key</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">halo</span><span style="color:#6A737D"> #应用的 key ,仅限英文,用于在 Linux 创建文件夹</span></span>
<span class="line"><span style="color:#85E89D"> name</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Halo</span><span style="color:#6A737D"> #应用名称</span></span>
<span class="line"><span style="color:#85E89D"> tags</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">WebSite</span><span style="color:#6A737D"> #应用标签,可以有多个,请参照下方的标签列表 </span></span>
<span class="line"><span style="color:#85E89D"> description</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Powerful and easy-to-use open source website builder</span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">强大易用的开源建站工具</span><span style="color:#6A737D"> #应用中文描述,不要超过30个字</span></span>
<span class="line"><span style="color:#85E89D"> zh-Hant</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">website</span><span style="color:#6A737D"> #应用类型,区别于应用分类,只能有一个,请参照下方的类型列表</span></span>
<span class="line"><span style="color:#85E89D"> crossVersionUpdate</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#6A737D"> #是否可以跨大版本升级</span></span>
<span class="line"><span style="color:#85E89D"> limit</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">0</span><span style="color:#6A737D"> #应用安装数量限制,0 代表无限制</span></span>
<span class="line"><span style="color:#85E89D"> website</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">https://halo.run/</span><span style="color:#6A737D"> #官网地址</span></span>
<span class="line"><span style="color:#85E89D"> github</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">https://github.com/halo-dev/halo</span><span style="color:#6A737D"> #github 地址 </span></span>
<span class="line"><span style="color:#85E89D"> document</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">https://docs.halo.run/</span><span style="color:#6A737D"> #文档地址</span></span></code></pre>
<h2 id="应用标签---tags-字段">应用标签 - tags 字段</h2>
<table><thead><tr><th>key</th><th>name</th></tr></thead><tbody><tr><td>WebSite</td><td>建站</td></tr><tr><td>Server</td><td>Web 服务器</td></tr><tr><td>Runtime</td><td>运行环境</td></tr><tr><td>Database</td><td>数据库</td></tr><tr><td>Tool</td><td>工具</td></tr><tr><td>CI/CD</td><td>CI/CD</td></tr><tr><td>Local</td><td>本地</td></tr></tbody></table>
<h1 id="应用类型---type-字段">应用类型 - type 字段</h1>
<table><thead><tr><th>type</th><th>说明</th></tr></thead><tbody><tr><td>website</td><td>website 类型在 1Panel 中支持在网站中一键部署,wordpress halo 都是此 type</td></tr><tr><td>runtime</td><td>mysql openresty redis 等类型的应用</td></tr><tr><td>tool</td><td>phpMyAdmin redis-commander jenkins 等类型的应用</td></tr></tbody></table>
<h1 id="应用参数配置文件-datayml">应用参数配置文件 data.yml</h1>
<p>(注意区分于应用主目录下面的 <code>data.yaml</code></p>
<p>本文件主要用于生成安装时要填写的 form 表单,在应用版本文件夹下面 可以无表单,但是需要有这个 data.yml文件,并且包含 formFields 字段</p>
<p>以安装 halo 时的 form 表单 为例</p>
<p>如果要生成上面的表单,需要这么填写 <code>data.yml</code></p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="yml"><code><span class="line"><span style="color:#85E89D"> additionalProperties</span><span style="color:#E1E4E8">: </span><span style="color:#6A737D">#固定参数</span></span>
<span class="line"><span style="color:#85E89D"> formFields</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">""</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">PANEL_DB_HOST</span><span style="color:#6A737D"> #docker-compose 文件中的参数</span></span>
<span class="line"><span style="color:#85E89D"> key</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">mysql</span><span style="color:#6A737D"> #依赖应用的 key , 例如 mysql</span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Database Service</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">データベースサービス</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Perkhidmatan Pangkalan Data</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Serviço de Banco de Dados</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Сервис базы данных</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">데이터베이스 서비스</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">数据库服务</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-Hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">數據庫 服務</span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#6A737D"> #是否必填</span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">service</span><span style="color:#6A737D"> #如果需要依赖其他应用,例如数据库,使用此 type </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">halo</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">PANEL_DB_NAME</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Database</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">データベース</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Pangkalan Data</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Banco de Dados</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">База данных</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">데이터베이스</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">数据库名</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-Hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">資料庫名稱</span></span>
<span class="line"><span style="color:#85E89D"> random</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#6A737D"> #是否在 default 文字后面,增加随机字符串</span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> rule</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">paramCommon</span><span style="color:#6A737D"> #校验规则</span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">text</span><span style="color:#6A737D"> #需要手动填写的,使用此 type</span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">halo</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">PANEL_DB_USER</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">User</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">ユーザー</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Pengguna</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Usuário</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Пользователь</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">사용자</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">数据库用户</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-Hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">資料庫使用者</span></span>
<span class="line"><span style="color:#85E89D"> random</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> rule</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">paramCommon</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">text</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">halo</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">PANEL_DB_USER_PASSWORD</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Password</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">パスワード</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Kata laluan</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Senha</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Пароль</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">비밀번호</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">数据库用户密码</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-Hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">資料庫使用者密碼</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> random</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> rule</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">paramComplexity</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">password</span><span style="color:#6A737D"> #密码字段使用此 type</span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">admin</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">HALO_ADMIN</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> labelEn</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Admin Username</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> labelZh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">超级管理员用户名</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> rule</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">paramCommon</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">text</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">http://localhost:8080</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> edit</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">HALO_EXTERNAL_URL</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">External URL</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">外部URL</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">URL Luaran</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">URL Externa</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Внешний URL</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">외부 URL</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">外部访问地址</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-Hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">外部訪問地址</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> rule</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">paramExtUrl</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">text</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">8080</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> edit</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">PANEL_APP_PORT_HTTP</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Port</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">ポート</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Port</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Porta</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Порт</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">포트</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">端口</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-Hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF"></span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> rule</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">paramPort</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">number</span><span style="color:#6A737D"> #端口使用此 type </span></span></code></pre>
<h2 id="关于端口字段">关于端口字段:</h2>
<p><code>PANEL_APP_PORT_HTTP</code><code>web</code> 访问端口的优先使用此 <code>envKey</code>
<code>envKey</code> 中包含 <code>PANEL_APP_PORT</code> 前缀会被认定为端口类型,并且用于安装前的端口占用校验。注意:端口需要是外部端口</p>
<h2 id="关于-type-字段">关于 type 字段:</h2>
<table><thead><tr><th>type</th><th>说明</th></tr></thead><tbody><tr><td>service</td><td>type: service 如果该应用需要依赖其他组件,如 mysql redis 等,可以通过 key: mysql 定义依赖的名称,在创建应用时会要求先创建依赖的应用。</td></tr><tr><td>password</td><td>type: password 敏感信息,如密码相关的字段会默认不显示明文。</td></tr><tr><td>text</td><td>type: text 一般内容,比如数据库名称,默认明文显示。</td></tr><tr><td>number</td><td>type: number 一般用在端口相关的配置上,只允许输入数字。</td></tr></tbody></table>
<p>简单的例子</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="yml"><code><span class="line"><span style="color:#6A737D"># type: service,定义一个 mysql 的 service 依赖。</span></span>
<span class="line"><span style="color:#E1E4E8">- </span><span style="color:#85E89D">child</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">""</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">PANEL_DB_HOST</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">service</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">mysql</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">PANEL_DB_TYPE</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Database Service</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">データベースサービス</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Perkhidmatan Pangkalan Data</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Serviço de Banco de Dados</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Сервис баз данных</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">데이터베이스 서비스</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">資料庫服務</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">数据库服务</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">apps</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> values</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">label</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">MySQL</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> value</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">mysql</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">label</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">MariaDB</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> value</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">mariadb</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># type: password</span></span>
<span class="line"><span style="color:#E1E4E8">- </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">word</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">PANEL_DB_USER_PASSWORD</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> labelEn</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Database Password</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> labelZh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">数据库密码</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Database Password</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">データベースのパスワード</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Kata Laluan Pangkalan Data</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Senha do Banco de Dados</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Пароль базы данных</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">데이터베이스 비밀번호</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">資料庫密碼</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">数据库密码</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> random</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">password</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># type: text</span></span>
<span class="line"><span style="color:#E1E4E8">- </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">""</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> edit</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">REDIS_HOST</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> key</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">redis</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">service</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Redis Service</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Redis サービス</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Perkhidmatan Redis</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Serviço Redis</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Сервис Redis</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Redis 서비스</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-Hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Redis 服務</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Redis 服务</span></span>
<span class="line"><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D"># type: number</span></span>
<span class="line"><span style="color:#E1E4E8">- </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">""</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> edit</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">DBHUB_DB_PORT</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">text</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> label</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> en</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Database Port</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ja</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">データベースのポート</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ms</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Pangkalan Data Port</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> pt-br</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Porta do Banco de Dados</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ru</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">Порт базы данных</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ko</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">데이터베이스 포트</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh-hant</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">資料庫端口</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> zh</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">数据库端口</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># type: select</span></span>
<span class="line"><span style="color:#E1E4E8">- </span><span style="color:#85E89D">default</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"ERROR"</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> envKey</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">LOG_LEVEL</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> required</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">select</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> values</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">label</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">DEBUG</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> value</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"DEBUG"</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">label</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">INFO</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> value</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"INFO"</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">label</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">WARNING</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> value</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"WARNING"</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">label</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">ERROR</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> value</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"ERROR"</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#85E89D">label</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">CRITICAL</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> value</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"CRITICAL"</span></span></code></pre>
<p>rule 字段目前支持的几种校验</p>
<table><thead><tr><th>rule</th><th>规则</th></tr></thead><tbody><tr><td>paramPort</td><td>用于限制端口范围为 1-65535</td></tr><tr><td>paramExtUrl</td><td>格式为 http(s)://(域名/ip):(端口)</td></tr><tr><td>paramCommon</td><td>英文、数字、.-和_,长度2-30</td></tr><tr><td>paramComplexity</td><td>支持英文、数字、.%@$!&#x26;~_-,长度6-30,特殊字符不能在首尾</td></tr></tbody></table>
<p>应用 <code>docker-compose.yml</code> 文件</p>
<p><code>${PANEL_APP_PORT_HTTP} </code>类型的参数,都在 <code>data.yml</code> 中有声明</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="yml"><code><span class="line"><span style="color:#85E89D">version</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"3"</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D">services</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> halo</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> image</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">halohub/halo:2.2.0</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> container_name</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">${CONTAINER_NAME} // 固定写法,勿改</span></span>
<span class="line"><span style="color:#85E89D"> restart</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">always</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> networks</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">1panel-network // 1Panel 创建的应用都在此网络下</span></span>
<span class="line"><span style="color:#85E89D"> volumes</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">./data:/root/.halo2</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> ports</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">${PANEL_APP_PORT_HTTP}:8090</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> command</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">--spring.r2dbc.url=r2dbc:pool:${HALO_PLATFORM}://${PANEL_DB_HOST}:${HALO_DB_PORT}/${PANEL_DB_NAME}</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">--spring.r2dbc.username=${PANEL_DB_USER}</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">--spring.r2dbc.password=${PANEL_DB_USER_PASSWORD}</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">--spring.sql.init.platform=${HALO_PLATFORM}</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">--halo.external-url=${HALO_EXTERNAL_URL}</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">--halo.security.initializer.superadminusername=${HALO_ADMIN}</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> - </span><span style="color:#9ECBFF">--halo.security.initializer.superadminpassword=${HALO_ADMIN_PASSWORD}</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D"> labels</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> createdBy</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Apps"</span><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#E1E4E8"> </span></span>
<span class="line"><span style="color:#85E89D">networks</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> 1panel-network</span><span style="color:#E1E4E8">: </span></span>
<span class="line"><span style="color:#85E89D"> external</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">true</span></span></code></pre>
<h2 id="脚本">脚本</h2>
<p>1Panel 在 安装之前、升级之前、卸载之后支持执行 .sh 脚本
分别对应 init.sh upgrade.sh uninstall.sh
存放目录(以halo为例) : halo/2.2.0/scripts</p>
<h1 id="本地测试">本地测试</h1>
<p>将应用目录上传到 1Panel 的 /opt/1panel/resource/apps/local 文件夹下
注意:/opt 为 1Panel 默认安装目录,请根据自己的实际情况修改
上传完成后,目录结构如下</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="plaintext"><code><span class="line"><span>├──halo </span></span>
<span class="line"><span> ├── logo.png </span></span>
<span class="line"><span> ├── data.yml</span></span>
<span class="line"><span> ├── README.md </span></span>
<span class="line"><span> ├── 2.2.0 </span></span>
<span class="line"><span> ├── data.yml </span></span>
<span class="line"><span> ├── data </span></span>
<span class="line"><span> └── docker-compose.yml</span></span></code></pre>
<p>在 1Panel 应用商店中,点击更新应用列表按钮同步本地应用</p>
<p>v1.2 版本及之前版本的本地应用,请参考这个文档修改</p>
<h1 id="提交文件">提交文件</h1>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> add</span><span style="color:#9ECBFF"> .</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> commit</span><span style="color:#79B8FF"> -m</span><span style="color:#9ECBFF"> "Add my-app"</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> push</span><span style="color:#9ECBFF"> origin</span><span style="color:#9ECBFF"> dev</span></span></code></pre>
<h1 id="提交-pull-request">提交 Pull Request</h1>
<p>在你的仓库点击 Pull requests 菜单
点击 New pull request ,填写标题和描述
选择由你的分支提交到 1Panel-dev/appstore</p> </div> <section class="post-copyright-card" aria-label="文章版权信息"> <div class="post-copyright-mark" aria-hidden="true"> <span>CC</span> </div> <div class="post-copyright-main"> <h2>为1Panel添加自己想要的应用</h2> <a class="post-copyright-link" href="/posts/bb18d851/">/posts/bb18d851/</a> <dl class="post-copyright-meta"> <div> <dt>作者</dt> <dd>biss</dd> </div> <div> <dt>发布于</dt> <dd><time datetime="2025-08-19T10:43:16.000Z">2025/08/19</time></dd> </div> <div> <dt>许可协议</dt> <dd><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="noreferrer">CC BY-NC-SA 4.0</a></dd> </div> </dl> </div> <div class="post-copyright-footer"> <div class="post-copyright-tags" aria-label="文章标签"> <a href="/tags/1panel/">1panel</a> </div> <div class="post-copyright-actions" aria-label="文章操作"> <button class="post-copyright-action is-primary post-share-button" type="button" data-share-title="为1Panel添加自己想要的应用" data-share-url="https://blog.biss.click/posts/bb18d851/" title="分享文章"> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"></path> <path d="M12 16V4"></path> <path d="m7 9 5-5 5 5"></path> </svg> <span>分享</span> </button> <a class="post-copyright-action" href="/rss.xml" title="订阅 RSS"> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M5 5a14 14 0 0 1 14 14"></path> <path d="M5 11a8 8 0 0 1 8 8"></path> <path d="M6 18h.01"></path> </svg> <span>订阅</span> </a> <a class="post-copyright-action" href="/about/" title="打赏作者"> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M7 8h10"></path> <path d="M8 8V5h8v3"></path> <path d="M6 8h12l-1.5 11h-9L6 8Z"></path> <path d="M10 12h4"></path> </svg> <span>打赏</span> </a> <a class="post-copyright-icon" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.biss.click%2Fposts%2Fbb18d851%2F&amp;text=%E4%B8%BA1Panel%E6%B7%BB%E5%8A%A0%E8%87%AA%E5%B7%B1%E6%83%B3%E8%A6%81%E7%9A%84%E5%BA%94%E7%94%A8" target="_blank" rel="noreferrer" title="分享到 X"> <span aria-hidden="true">x</span> </a> <a class="post-copyright-icon" href="https://service.weibo.com/share/share.php?url=https%3A%2F%2Fblog.biss.click%2Fposts%2Fbb18d851%2F&amp;title=%E4%B8%BA1Panel%E6%B7%BB%E5%8A%A0%E8%87%AA%E5%B7%B1%E6%83%B3%E8%A6%81%E7%9A%84%E5%BA%94%E7%94%A8" target="_blank" rel="noreferrer" title="分享到微博"> <span aria-hidden="true"></span> </a> </div> </div> </section> <script>
(() => {
document.querySelectorAll('.post-share-button').forEach((button) => {
button.addEventListener('click', async () => {
const title = button.dataset.shareTitle || document.title;
const url = button.dataset.shareUrl || window.location.href;
const label = button.querySelector('span');
const originalText = label?.textContent || '分享';
try {
if (navigator.share) {
await navigator.share({ title, url });
return;
}
await navigator.clipboard.writeText(url);
if (label) label.textContent = '已复制';
} catch {
if (label) label.textContent = '复制失败';
} finally {
window.setTimeout(() => {
if (label) label.textContent = originalText;
}, 1800);
}
});
});
})();
</script> <section id="post-comment" class="twikoo-comments" aria-label="Comments" data-env-id="https://comment.biss.click" data-region data-path="/posts/bb18d851/" data-script="https://cdn.jsdelivr.net/npm/twikoo@1.7.7/dist/twikoo.min.js"> <div class="twikoo-comments-head"> <span class="twikoo-comments-icon" aria-hidden="true"> <svg viewBox="0 0 24 24"> <path d="M21 12a8 8 0 0 1-8 8H7l-4 3v-5a8 8 0 1 1 18-6Z"></path> </svg> </span> <div> <h2>Comments</h2> <p>为1Panel添加自己想要的应用</p> </div> </div> <div id="twikoo-wrap" class="twikoo-wrap"></div> </section> <script>
(() => {
const root = document.getElementById('post-comment');
const wrap = document.getElementById('twikoo-wrap');
if (!root || !wrap || root.dataset.twikooLoaded === 'true') return;
const envId = root.dataset.envId;
const path = root.dataset.path || window.location.pathname;
const region = root.dataset.region || undefined;
if (!envId) return;
root.dataset.twikooLoaded = 'true';
const initTwikoo = () => {
if (!window.twikoo || typeof window.twikoo.init !== 'function') return;
window.twikoo.init({
el: wrap,
envId,
region,
path,
});
};
if (window.twikoo) {
initTwikoo();
return;
}
const script = document.createElement('script');
script.src = root.dataset.script;
script.defer = true;
script.onload = initTwikoo;
script.onerror = () => {
root.dataset.twikooLoaded = 'false';
};
document.head.appendChild(script);
})();
</script> </article> <aside class="article-sidebar" aria-label="文章侧边栏"> <section class="sidebar-card article-author-card"> <img class="profile-avatar" src="https://free.picui.cn/free/2025/08/10/689845496a283.png" alt="biss" loading="lazy"> <div class="profile-name">biss</div> <div class="article-author-meta"> <span>2025/08/19</span> <a href="/categories/tech/">技术</a> </div> </section> <section class="sidebar-card article-tags-card"> <h2> <span aria-hidden="true">#</span>
文章标签
<em>1</em> </h2> <div class="article-tag-list" aria-label="文章标签"> <a href="/tags/1panel/">#1panel</a> </div> </section> <div class="article-sticky-sidebar"> <section class="sidebar-card toc-card"> <h2> <span aria-hidden="true"></span>
文章目录
<em>13</em> </h2> <nav class="toc-list toc-tree" aria-label="文章目录" data-toc-tree> <div class="toc-group is-open" data-toc-group data-heading-id="预准备"> <a class="toc-link toc-link-main" href="#预准备" data-toc-link data-toc-top> <span class="toc-marker" aria-hidden="true"></span> <span>预准备</span> </a> </div><div class="toc-group" data-toc-group data-heading-id="创建文件"> <a class="toc-link toc-link-main" href="#创建文件" data-toc-link data-toc-top> <span class="toc-marker" aria-hidden="true"></span> <span>创建文件</span> </a> <div class="toc-children" data-toc-children> <a class="toc-link toc-link-child toc-depth-2" href="#文件目录" data-toc-link> <span>文件目录</span> </a><a class="toc-link toc-link-child toc-depth-2" href="#应用声明文件datayml" data-toc-link> <span>应用声明文件`data.yml`</span> </a><a class="toc-link toc-link-child toc-depth-2" href="#应用标签-tags-字段" data-toc-link> <span>应用标签 - tags 字段</span> </a> </div> </div><div class="toc-group" data-toc-group data-heading-id="应用类型-type-字段"> <a class="toc-link toc-link-main" href="#应用类型-type-字段" data-toc-link data-toc-top> <span class="toc-marker" aria-hidden="true"></span> <span>应用类型 - type 字段</span> </a> </div><div class="toc-group" data-toc-group data-heading-id="应用参数配置文件-datayml"> <a class="toc-link toc-link-main" href="#应用参数配置文件-datayml" data-toc-link data-toc-top> <span class="toc-marker" aria-hidden="true"></span> <span>应用参数配置文件 data.yml</span> </a> <div class="toc-children" data-toc-children> <a class="toc-link toc-link-child toc-depth-2" href="#关于端口字段" data-toc-link> <span>关于端口字段:</span> </a><a class="toc-link toc-link-child toc-depth-2" href="#关于-type-字段" data-toc-link> <span>关于 type 字段:</span> </a><a class="toc-link toc-link-child toc-depth-2" href="#脚本" data-toc-link> <span>脚本</span> </a> </div> </div><div class="toc-group" data-toc-group data-heading-id="本地测试"> <a class="toc-link toc-link-main" href="#本地测试" data-toc-link data-toc-top> <span class="toc-marker" aria-hidden="true"></span> <span>本地测试</span> </a> </div><div class="toc-group" data-toc-group data-heading-id="提交文件"> <a class="toc-link toc-link-main" href="#提交文件" data-toc-link data-toc-top> <span class="toc-marker" aria-hidden="true"></span> <span>提交文件</span> </a> </div><div class="toc-group" data-toc-group data-heading-id="提交-pull-request"> <a class="toc-link toc-link-main" href="#提交-pull-request" data-toc-link data-toc-top> <span class="toc-marker" aria-hidden="true"></span> <span>提交 Pull Request</span> </a> </div> </nav> </section> <section class="sidebar-card same-category-card"> <h2> <span aria-hidden="true"></span>
同类文章
<em>8</em> </h2> <div class="same-category-list"> <a href="/posts/3f8de22/"> <strong>GPT Image2 初试</strong> <time datetime="2026-04-25T15:00:00.000Z">2026/04/25</time> </a><a href="/posts/8712baaa/"> <strong>电子价签玩法</strong> <time datetime="2026-04-19T13:46:25.000Z">2026/04/19</time> </a><a href="/posts/9b2ef00a/"> <strong>OpenGL- Cohen-Sutherland 编码裁剪算法</strong> <time datetime="2026-04-15T19:34:23.000Z">2026/04/16</time> </a><a href="/posts/7207243b/"> <strong>OpenGL-直线的扫描转换</strong> <time datetime="2026-04-11T19:01:02.000Z">2026/04/12</time> </a><a href="/posts/437a5198/"> <strong>OpenGL-基础程序</strong> <time datetime="2026-04-11T18:20:34.000Z">2026/04/12</time> </a><a href="/posts/c4477b0c/"> <strong>配置OpenGL环境</strong> <time datetime="2026-04-02T04:52:16.000Z">2026/04/02</time> </a><a href="/posts/b559997d/"> <strong>opencv应用-算术运算</strong> <time datetime="2026-03-17T13:13:08.000Z">2026/03/17</time> </a><a href="/posts/e8f95ead/"> <strong>opencv应用-基础操作</strong> <time datetime="2026-03-15T09:56:51.000Z">2026/03/15</time> </a> </div> </section> <section class="sidebar-card site-info-card" data-site-info-card data-stats-endpoint="https://blogumami.biss.click"> <h2><i class="fa-solid fa-chart-line" aria-hidden="true"></i> 网站信息</h2> <dl class="site-info-list"> <div> <dt>文章数目</dt> <dd>44</dd> </div><div> <dt>本站总字数</dt> <dd>36,128</dd> </div><div> <dt>本站访客数</dt> <dd data-stat-key="visitors">--</dd> </div><div> <dt>本站访问量</dt> <dd data-stat-key="pageviews">--</dd> </div><div> <dt>最后更新时间</dt> <dd>2026/06/20</dd> </div> </dl> </section> <script>
(() => {
const cacheDuration = 10 * 60 * 1000;
const formatter = new Intl.NumberFormat('en-US');
const cards = [...document.querySelectorAll('[data-site-info-card]')];
const endpoint = cards.find((card) => card.dataset.statsEndpoint)?.dataset.statsEndpoint;
if (!endpoint) return;
const cacheKey = `site-info-stats:${endpoint}`;
const updateCards = (stats) => {
const visitors = Number(stats?.visitors);
const pageviews = Number(stats?.pageviews);
cards.forEach((card) => {
const visitorsEl = card.querySelector('[data-stat-key="visitors"]');
const pageviewsEl = card.querySelector('[data-stat-key="pageviews"]');
if (visitorsEl && Number.isFinite(visitors)) visitorsEl.textContent = formatter.format(visitors);
if (pageviewsEl && Number.isFinite(pageviews)) pageviewsEl.textContent = formatter.format(pageviews);
});
};
try {
const cached = JSON.parse(localStorage.getItem(cacheKey) || 'null');
if (cached?.time && Date.now() - cached.time < cacheDuration) {
updateCards(cached.data);
return;
}
} catch {
localStorage.removeItem(cacheKey);
}
fetch(endpoint, { headers: { Accept: 'application/json' } })
.then((response) => {
if (!response.ok) throw new Error(`Stats request failed: ${response.status}`);
return response.json();
})
.then((data) => {
localStorage.setItem(cacheKey, JSON.stringify({ time: Date.now(), data }));
updateCards(data);
})
.catch(() => {
});
})();
</script> </div> </aside> </section> </main> <footer class="site-footer" aria-label="站点页脚"> <div class="footer-inner"> <section class="footer-top" aria-label="站点信息"> <a class="footer-brand" href="/" aria-label="Bi's Blog 首页"> <img src="/images/Bi.ico" alt="" loading="lazy"> <span>Bi&#39;s Blog</span> </a> <p class="footer-slogan">好奇心转化为代码的空间。</p> </section> <section class="footer-contact" aria-label="联系方式"> <p>联系方式</p> <div class="footer-socials"> <a href="mailto:bishsh2006@gmail.com" aria-label="发送邮件" title="发送邮件"> <i class="fa-solid fa-envelope" aria-hidden="true"></i> </a><a href="/rss.xml" aria-label="订阅 RSS" title="订阅 RSS"> <i class="fa-solid fa-rss" aria-hidden="true"></i> </a><a href="https://github.com/bishshi" target="_blank" rel="noreferrer" aria-label="GitHub" title="GitHub"> <i class="fa-brands fa-github" aria-hidden="true"></i> </a> </div> <nav class="footer-contact-links" aria-label="页脚链接"> <a href="/archives/">发展历程</a> <span aria-hidden="true">|</span><a href="/privacy/">隐私政策</a> </nav> </section> <section class="footer-bottom" aria-label="版权信息"> <div> <p>© 2025 - 2026 Bi&#39;s Blog</p> <p>blog.biss.click | 内容采用 CC BY-NC-SA 4.0 协议</p> </div> <div class="footer-meta"> <p>本站已运行:<span id="site-runtime">计算中...</span></p> <p> <a href="https://fastly.com/" target="_blank" rel="noreferrer">Fastly</a> <span aria-hidden="true">|</span><a href="https://astro.build/" target="_blank" rel="noreferrer">ASTRO</a> </p> </div> </section> </div> </footer> <aside class="hitokoto-card is-collapsed" aria-label="一言" data-hitokoto-card data-astro-cid-4cgfuthx> <div class="hitokoto-card__head" data-astro-cid-4cgfuthx> <button class="hitokoto-card__toggle" type="button" aria-label="展开一言" aria-expanded="false" title="一言" data-hitokoto-toggle data-astro-cid-4cgfuthx> <i class="fa-solid fa-quote-left" aria-hidden="true" data-astro-cid-4cgfuthx></i> <span data-astro-cid-4cgfuthx>一言</span> </button> <div class="hitokoto-card__actions" data-astro-cid-4cgfuthx> <button class="hitokoto-card__refresh" type="button" aria-label="换一句" title="换一句" data-hitokoto-refresh data-astro-cid-4cgfuthx> <i class="fa-solid fa-rotate" aria-hidden="true" data-astro-cid-4cgfuthx></i> </button> <button class="hitokoto-card__collapse" type="button" aria-label="收起一言" title="收起" data-hitokoto-collapse data-astro-cid-4cgfuthx> <i class="fa-solid fa-chevron-down" aria-hidden="true" data-astro-cid-4cgfuthx></i> </button> </div> </div> <a class="hitokoto-card__text" href="https://hitokoto.cn/" target="_blank" rel="noreferrer" data-hitokoto-text data-astro-cid-4cgfuthx>
获取中...
</a> <p class="hitokoto-card__from" data-hitokoto-from data-astro-cid-4cgfuthx>来自 Hitokoto</p> </aside> <script type="module">(()=>{const o=document.querySelector("[data-hitokoto-card]"),c=o?.querySelector("[data-hitokoto-toggle]"),s=o?.querySelector("[data-hitokoto-text]"),l=o?.querySelector("[data-hitokoto-from]"),g=o?.querySelector("[data-hitokoto-refresh]"),k=o?.querySelector("[data-hitokoto-collapse]");if(!(o instanceof HTMLElement)||!(c instanceof HTMLButtonElement)||!(s instanceof HTMLAnchorElement)||!(l instanceof HTMLElement))return;const y="https://yiyan.biss.click/api?encode=json",u="hitokoto-card-cache",d="hitokoto-card-expanded",x=360*60*1e3,f={sentence:"生活明朗,万物可爱。",source:"本地缓存",url:"https://hitokoto.cn/"},S=["/","/index.html"].includes(window.location.pathname),w=window.matchMedia("(max-width: 720px)");function E(){try{const t=localStorage.getItem(d);if(t==="true")return!0;if(t==="false")return!1}catch{}return null}function L(t){try{localStorage.setItem(d,String(t))}catch{}}function r(t,{persist:e=!1}={}){o.classList.toggle("is-collapsed",!t),c.setAttribute("aria-expanded",String(t)),c.setAttribute("aria-label",t?"收起一言":"展开一言"),e&&L(t)}function h(){try{const t=localStorage.getItem(u);if(!t)return null;const e=JSON.parse(t);return!e||typeof e.expiresAt!="number"||e.expiresAt<=Date.now()?null:e.data}catch{return null}}function H(t){try{localStorage.setItem(u,JSON.stringify({data:t,expiresAt:Date.now()+x}))}catch{}}function i(t){s.textContent=t.sentence,s.href=t.url||"https://hitokoto.cn/",l.textContent=t.source?`出自 ${t.source}`:"来自 Hitokoto"}async function p({force:t=!1}={}){if(!t){const e=h();if(e){i(e);return}}o.classList.add("is-loading"),s.textContent="获取中...";try{const e=await fetch(y);if(!e.ok)throw new Error(`Hitokoto request failed: ${e.status}`);const n=await e.json(),a=n.hitokoto||n.content||n.sentence||n.text,q=typeof a=="string"&&a.trim()?a.trim():f.sentence,A=typeof n.from=="string"&&n.from.trim()?n.from.trim():"Hitokoto",M=n.uuid?`https://hitokoto.cn/?uuid=${encodeURIComponent(n.uuid)}`:"https://hitokoto.cn/",m={sentence:q,source:A,url:M};H(m),i(m)}catch(e){console.error(e),i(h()||f)}finally{o.classList.remove("is-loading")}}const b=E();r(b??(S&&!w.matches)),c.addEventListener("click",()=>{r(o.classList.contains("is-collapsed"),{persist:!0})}),g?.addEventListener("click",t=>{t.stopPropagation(),r(!0,{persist:!0}),p({force:!0})}),k?.addEventListener("click",t=>{t.stopPropagation(),r(!1,{persist:!0})}),o.addEventListener("click",t=>{o.classList.contains("is-collapsed")&&(t.target instanceof Element&&t.target.closest("a, button")||r(!0,{persist:!0}))}),p()})();</script> <div class="custom-context-menu" data-context-menu role="menu" aria-hidden="true"> <div class="context-menu-nav" aria-label="页面导航"> <button type="button" data-context-action="back" aria-label="后退上一页" title="后退上一页"> <i class="fa-solid fa-arrow-left" aria-hidden="true"></i> </button> <button type="button" data-context-action="forward" aria-label="前进下一页" title="前进下一页"> <i class="fa-solid fa-arrow-right" aria-hidden="true"></i> </button> <button type="button" data-context-action="reload" aria-label="刷新页面" title="刷新页面"> <i class="fa-solid fa-rotate-right" aria-hidden="true"></i> </button> <a href="/" aria-label="返回首页" title="返回首页"> <i class="fa-solid fa-house" aria-hidden="true"></i> </a> </div> <div class="context-menu-group" data-selection-group hidden> <button type="button" data-context-action="copy-selection" data-selection-item> <i class="fa-regular fa-copy" aria-hidden="true"></i> <span>复制选中文字</span> </button> <button type="button" data-context-action="search-selection" data-selection-item> <i class="fa-brands fa-microsoft" aria-hidden="true"></i> <span>用 Bing 搜索</span> </button> <button type="button" data-context-action="quote-selection" data-selection-item data-comments-item> <i class="fa-regular fa-comment-dots" aria-hidden="true"></i> <span>引用到评论区</span> </button> </div> <div class="context-menu-group" data-image-group hidden> <button type="button" data-context-action="copy-image"> <i class="fa-regular fa-image" aria-hidden="true"></i> <span>复制图片</span> </button> <button type="button" data-context-action="open-image"> <i class="fa-solid fa-up-right-from-square" aria-hidden="true"></i> <span>新页面打开图片</span> </button> <button type="button" data-context-action="copy-image-url"> <i class="fa-solid fa-link" aria-hidden="true"></i> <span>复制图片地址</span> </button> </div> <div class="context-menu-group"> <a href="/archives/"> <i class="fa-solid fa-box-archive" aria-hidden="true"></i> <span>时间线</span> </a> <a href="/categories/"> <i class="fa-solid fa-folder" aria-hidden="true"></i> <span>分类</span> </a> <a href="/tags/"> <i class="fa-solid fa-hashtag" aria-hidden="true"></i> <span>标签</span> </a> <button type="button" data-context-action="random-post"> <i class="fa-solid fa-shuffle" aria-hidden="true"></i> <span>&#x968F;&#x673A;&#x6587;&#x7AE0;</span> </button> <button type="button" data-context-action="comments" data-comments-item> <i class="fa-regular fa-comment-dots" aria-hidden="true"></i> <span>跳转评论区</span> </button> <button type="button" data-context-action="print"> <i class="fa-solid fa-print" aria-hidden="true"></i> <span>打印页面</span> </button> <a href="/cc/"> <i class="fa-solid fa-circle-info" aria-hidden="true"></i> <span>网站版权</span> </a> </div> </div> <div class="context-menu-native-hint" data-context-native-hint role="note" aria-label="Ctrl 右键提示"> <kbd>Ctrl</kbd> + &#x53F3;&#x952E;&#xFF1A;&#x6D4F;&#x89C8;&#x5668;&#x539F;&#x751F;&#x83DC;&#x5355;
</div> <script>
(() => {
const menu = document.querySelector('[data-context-menu]');
if (!menu) return;
const nativeHint = document.querySelector('[data-context-native-hint]');
const selectionItems = [...menu.querySelectorAll('[data-selection-item]')];
const selectionGroup = menu.querySelector('[data-selection-group]');
const commentsItems = [...menu.querySelectorAll('[data-comments-item]')];
const imageGroup = menu.querySelector('[data-image-group]');
const focusableSelector = 'a[href], button:not([disabled])';
let lastSelectionText = '';
let lastImage = null;
let nativeHintTimer = 0;
const isNativeContextTarget = (target) =>
target instanceof Element &&
Boolean(target.closest('input, textarea, select, [contenteditable="true"], [contenteditable=""]'));
const getCommentsTarget = () =>
document.querySelector('.twikoo-comments, #twikoo-wrap, #twikoo, .tk-comments-container');
const getSelectionText = () => String(window.getSelection?.() || '').trim();
const getImageTarget = (target) => {
if (!(target instanceof Element)) return null;
const image = target.closest('img');
return image instanceof HTMLImageElement ? image : null;
};
const getImageUrl = () => {
if (!lastImage) return '';
return lastImage.currentSrc || lastImage.src || lastImage.getAttribute('src') || '';
};
const copyText = async (text) => {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.inset = '0 auto auto -9999px';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();
};
const setMenuState = () => {
const hasSelection = lastSelectionText.length > 0;
const hasComments = Boolean(getCommentsTarget());
const hasImage = Boolean(getImageUrl());
selectionItems.forEach((item) => {
item.hidden = !hasSelection;
});
if (selectionGroup) selectionGroup.hidden = !hasSelection;
commentsItems.forEach((item) => {
item.hidden = !hasComments || (item.hasAttribute('data-selection-item') && !hasSelection);
});
if (imageGroup) imageGroup.hidden = !hasImage;
};
const closeMenu = () => {
menu.classList.remove('is-open');
menu.setAttribute('aria-hidden', 'true');
};
const openMenu = (x, y) => {
setMenuState();
menu.classList.add('is-open');
menu.setAttribute('aria-hidden', 'false');
const rect = menu.getBoundingClientRect();
const gap = 10;
const left = Math.min(Math.max(gap, x), window.innerWidth - rect.width - gap);
const top = Math.min(Math.max(gap, y), window.innerHeight - rect.height - gap);
menu.style.left = `${left}px`;
menu.style.top = `${top}px`;
};
const showNativeHint = () => {
if (!nativeHint) return;
window.clearTimeout(nativeHintTimer);
nativeHint.classList.add('is-visible');
nativeHintTimer = window.setTimeout(() => {
nativeHint.classList.remove('is-visible');
}, 2600);
};
const jumpToComments = () => {
const target = getCommentsTarget();
target?.scrollIntoView({ behavior: 'smooth', block: 'start' });
};
const quoteToComments = () => {
jumpToComments();
window.setTimeout(() => {
const textarea = document.querySelector('#twikoo textarea, .twikoo-comments textarea, .tk-input textarea');
if (!(textarea instanceof HTMLTextAreaElement) || !lastSelectionText) return;
const quote = lastSelectionText
.split(/\r?\n/)
.map((line) => `> ${line}`)
.join('\n');
const current = textarea.value.trim();
textarea.value = current ? `${current}\n\n${quote}\n` : `${quote}\n`;
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.focus();
}, 500);
};
const searchSelection = () => {
if (!lastSelectionText) return;
const url = `https://www.bing.com/search?q=${encodeURIComponent(lastSelectionText)}`;
window.open(url, '_blank', 'noopener,noreferrer');
};
const copyImage = async () => {
const imageUrl = getImageUrl();
if (!imageUrl) return;
try {
if (!navigator.clipboard?.write || typeof ClipboardItem === 'undefined') {
throw new Error('Image clipboard is not available.');
}
const response = await fetch(imageUrl, { mode: 'cors' });
const blob = await response.blob();
await navigator.clipboard.write([
new ClipboardItem({ [blob.type || 'image/png']: blob }),
]);
} catch {
await copyText(new URL(imageUrl, window.location.href).toString());
}
};
const openImage = () => {
const imageUrl = getImageUrl();
if (!imageUrl) return;
window.open(new URL(imageUrl, window.location.href).toString(), '_blank', 'noopener,noreferrer');
};
document.addEventListener('contextmenu', (event) => {
if (event.ctrlKey) {
closeMenu();
nativeHint?.classList.remove('is-visible');
return;
}
if (isNativeContextTarget(event.target)) return;
lastSelectionText = getSelectionText();
lastImage = getImageTarget(event.target);
event.preventDefault();
openMenu(event.clientX, event.clientY);
showNativeHint();
});
document.addEventListener('click', (event) => {
const actionTarget = event.target instanceof Element ? event.target.closest('[data-context-action]') : null;
if (!actionTarget || !menu.contains(actionTarget)) {
closeMenu();
return;
}
const action = actionTarget.dataset.contextAction;
closeMenu();
if (action === 'back') history.back();
if (action === 'forward') history.forward();
if (action === 'reload') location.reload();
if (action === 'copy-selection' && lastSelectionText) copyText(lastSelectionText);
if (action === 'search-selection') searchSelection();
if (action === 'quote-selection') quoteToComments();
if (action === 'copy-image') copyImage();
if (action === 'open-image') openImage();
if (action === 'copy-image-url') copyText(new URL(getImageUrl(), window.location.href).toString());
if (action === 'random-post' && typeof window.randomPost === 'function') window.randomPost(actionTarget);
if (action === 'comments') jumpToComments();
if (action === 'print') window.print();
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') closeMenu();
if (event.key === 'Tab' && menu.classList.contains('is-open')) {
const items = [...menu.querySelectorAll(focusableSelector)].filter((item) => !item.closest('[hidden]'));
if (items.length === 0) return;
const first = items[0];
const last = items[items.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
}
});
window.addEventListener('scroll', closeMenu, { passive: true });
window.addEventListener('resize', closeMenu);
})();
</script> <aside class="pwa-toast" data-pwa-toast hidden role="status" aria-live="polite"> <div class="pwa-toast-icon" aria-hidden="true"><i class="fa-solid fa-download"></i></div> <div class="pwa-toast-copy"> <strong data-pwa-title>PWA 提示</strong> <p data-pwa-message></p> </div> <div class="pwa-toast-actions"> <button class="pwa-toast-primary" type="button" data-pwa-primary>确定</button> <button class="pwa-toast-close" type="button" data-pwa-close aria-label="关闭提示">稍后</button> </div> </aside> <script src="https://cdn.jsdmirror.com/npm/instantsearch.js@4.56.0"></script> <script src="https://cdn.jsdmirror.com/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"></script> <script src="https://cdn.jsdmirror.com/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js"></script> <script src="/js/random.js"></script> <script src="/js/search/typesense-search.js"></script> <script src="/js/external-links.js"></script> <script src="/js/pwa.js"></script> <script>
(() => {
const nav = document.querySelector('.nav');
const toggle = document.querySelector('[data-nav-toggle]');
const menu = document.querySelector('[data-nav-menu]');
const backdrop = document.querySelector('[data-nav-backdrop]');
if (!nav || !toggle || !menu) return;
function setMenu(open) {
nav.classList.toggle('is-open', open);
toggle.setAttribute('aria-expanded', String(open));
toggle.setAttribute('aria-label', open ? '关闭菜单' : '打开菜单');
menu.setAttribute('aria-hidden', String(!open && window.innerWidth <= 720));
document.body.classList.toggle('has-nav-drawer', open);
}
toggle.addEventListener('click', () => {
setMenu(!nav.classList.contains('is-open'));
});
backdrop?.addEventListener('click', () => setMenu(false));
menu.addEventListener('click', (event) => {
if (event.target instanceof Element && event.target.closest('a')) setMenu(false);
});
document.addEventListener('click', (event) => {
if (!nav.classList.contains('is-open')) return;
if (event.target instanceof Node && nav.contains(event.target)) return;
setMenu(false);
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && nav.classList.contains('is-open')) setMenu(false);
});
window.addEventListener('resize', () => {
if (window.innerWidth > 720) setMenu(false);
else menu.setAttribute('aria-hidden', String(!nav.classList.contains('is-open')));
});
menu.setAttribute('aria-hidden', String(window.innerWidth <= 720));
})();
(() => {
const modal = document.querySelector('[data-console-modal]');
const panel = modal?.querySelector('.control-console-panel');
const openButtons = document.querySelectorAll('[data-console-open]');
const closeButtons = modal?.querySelectorAll('[data-console-close]') || [];
if (!modal || !panel || openButtons.length === 0) return;
function setConsole(open) {
modal.classList.toggle('is-open', open);
modal.setAttribute('aria-hidden', String(!open));
document.body.classList.toggle('has-control-console', open);
if (open) panel.focus();
}
openButtons.forEach((button) => {
button.addEventListener('click', () => setConsole(true));
});
closeButtons.forEach((button) => {
button.addEventListener('click', () => setConsole(false));
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && modal.classList.contains('is-open')) setConsole(false);
});
})();
(() => {
const storageKey = 'site-theme';
const root = document.documentElement;
const button = document.querySelector('[data-theme-toggle]');
const media = window.matchMedia('(prefers-color-scheme: dark)');
const getSystemTheme = () => (media.matches ? 'dark' : 'light');
const getSavedTheme = () => {
try {
const theme = localStorage.getItem(storageKey);
return theme === 'dark' || theme === 'light' ? theme : null;
} catch {
return null;
}
};
const setSavedTheme = (theme) => {
try {
if (theme === getSystemTheme()) localStorage.removeItem(storageKey);
else localStorage.setItem(storageKey, theme);
} catch {
// localStorage can be unavailable in strict privacy modes.
}
};
const getTheme = () => getSavedTheme() || getSystemTheme();
function applyTheme(theme) {
root.dataset.theme = theme;
root.dataset.themeMode = getSavedTheme() ? 'manual' : 'auto';
button?.setAttribute('aria-label', theme === 'dark' ? '切换到日间模式' : '切换到夜间模式');
button?.setAttribute(
'title',
getSavedTheme()
? (theme === 'dark' ? '切换到日间模式' : '切换到夜间模式')
: `跟随系统:${theme === 'dark' ? '夜间' : '日间'}模式`
);
}
applyTheme(getTheme());
button?.addEventListener('click', () => {
const nextTheme = root.dataset.theme === 'dark' ? 'light' : 'dark';
setSavedTheme(nextTheme);
applyTheme(nextTheme);
});
const handleSystemThemeChange = () => {
if (!getSavedTheme()) applyTheme(getSystemTheme());
};
if (typeof media.addEventListener === 'function') {
media.addEventListener('change', handleSystemThemeChange);
} else if (typeof media.addListener === 'function') {
media.addListener(handleSystemThemeChange);
}
})();
(() => {
const runtime = document.getElementById('site-runtime');
if (!runtime) return;
const start = new Date(window.SITE_CONFIG.site.startTime).getTime();
const pad = (value) => String(value).padStart(2, '0');
function updateRuntime() {
const diff = Math.max(0, Date.now() - start);
const days = Math.floor(diff / 86400000);
const hours = Math.floor((diff / 3600000) % 24);
const minutes = Math.floor((diff / 60000) % 60);
const seconds = Math.floor((diff / 1000) % 60);
runtime.textContent = `${days}${pad(hours)}${pad(minutes)}${pad(seconds)} 秒`;
}
updateRuntime();
window.setInterval(updateRuntime, 1000);
})();
</script> </body> </html> <script>(function(){const postSlug = "bb18d851";
(() => {
const article = document.querySelector('.post-article');
const progressBar = document.querySelector('[data-reading-progress]');
const resume = document.querySelector('[data-reading-resume]');
const resumePercent = document.querySelector('[data-reading-resume-percent]');
const continueButton = document.querySelector('[data-reading-continue]');
const restartButton = document.querySelector('[data-reading-restart]');
if (!article || !progressBar || !resume) return;
const storageKey = `reading-progress:${postSlug}`;
let saved = null;
let canPersist = true;
let frame = 0;
try {
saved = JSON.parse(localStorage.getItem(storageKey) || 'null');
} catch {
saved = null;
}
const articleMetrics = () => {
const top = article.getBoundingClientRect().top + window.scrollY;
const distance = Math.max(1, article.offsetHeight - window.innerHeight);
return { top, distance };
};
const getProgress = () => {
const { top, distance } = articleMetrics();
return Math.min(1, Math.max(0, (window.scrollY - top) / distance));
};
const persist = (progress) => {
if (!canPersist) return;
try {
if (progress >= 0.97) localStorage.removeItem(storageKey);
else if (progress >= 0.02) localStorage.setItem(storageKey, JSON.stringify({ progress, savedAt: Date.now() }));
} catch {
// Storage can be unavailable in strict privacy modes.
}
};
const update = () => {
frame = 0;
const progress = getProgress();
progressBar.style.transform = `scaleX(${progress})`;
persist(progress);
};
const requestUpdate = () => {
if (!frame) frame = window.requestAnimationFrame(update);
};
const closeResume = () => {
resume.hidden = true;
canPersist = true;
};
const savedIsFresh = saved && Date.now() - Number(saved.savedAt) < 90 * 86400000;
const savedProgress = Number(saved?.progress);
if (savedIsFresh && savedProgress >= 0.05 && savedProgress < 0.97) {
canPersist = false;
if (resumePercent) resumePercent.textContent = `${Math.round(savedProgress * 100)}%`;
window.setTimeout(() => { resume.hidden = false; }, 350);
}
continueButton?.addEventListener('click', () => {
closeResume();
const { top, distance } = articleMetrics();
window.scrollTo({ top: top + distance * savedProgress, behavior: 'smooth' });
});
restartButton?.addEventListener('click', () => {
try { localStorage.removeItem(storageKey); } catch {}
closeResume();
window.scrollTo({ top: Math.max(0, articleMetrics().top - 90), behavior: 'smooth' });
});
update();
window.addEventListener('scroll', requestUpdate, { passive: true });
window.addEventListener('resize', requestUpdate);
window.addEventListener('pagehide', () => persist(getProgress()));
})();
})();</script> <script>
(() => {
document.querySelectorAll('.article-outdated-note[data-freshness-at]').forEach((note) => {
const freshnessAt = new Date(note.dataset.freshnessAt || '');
const hasNewVersion = note.dataset.hasNewVersion === 'true';
if (Number.isNaN(freshnessAt.valueOf())) return;
const threshold = new Date();
threshold.setFullYear(threshold.getFullYear() - 1);
note.hidden = !hasNewVersion && freshnessAt >= threshold;
});
})();
</script> <script>
(() => {
const prose = document.querySelector('.prose[data-toc]');
if (!prose) return;
let toc = [];
try {
toc = JSON.parse(prose.dataset.toc || '[]');
} catch {
toc = [];
}
prose.querySelectorAll('h1, h2, h3').forEach((heading, index) => {
const item = toc[index];
if (item?.id && !heading.id) heading.id = item.id;
});
const tocTree = document.querySelector('[data-toc-tree]');
if (tocTree && toc.length > 0) {
const links = [...tocTree.querySelectorAll('[data-toc-link]')];
const groups = [...tocTree.querySelectorAll('[data-toc-group]')];
const headings = toc
.map((item) => document.getElementById(item.id))
.filter(Boolean);
const setActiveToc = (id) => {
links.forEach((link) => {
const isActive = link.getAttribute('href') === `#${id}`;
link.classList.toggle('is-active', isActive);
if (isActive) link.setAttribute('aria-current', 'location');
else link.removeAttribute('aria-current');
});
const activeLink = tocTree.querySelector(`[data-toc-link][href="#${CSS.escape(id)}"]`);
const activeGroup = activeLink?.closest('[data-toc-group]');
groups.forEach((group) => group.classList.toggle('is-open', group === activeGroup));
};
const updateToc = () => {
const anchorOffset = 132;
let activeId = headings[0]?.id;
headings.forEach((heading) => {
if (heading.getBoundingClientRect().top <= anchorOffset) activeId = heading.id;
});
if (activeId) setActiveToc(activeId);
};
tocTree.addEventListener('click', (event) => {
const link = event.target.closest?.('[data-toc-link]');
if (!link) return;
const group = link.closest('[data-toc-group]');
groups.forEach((item) => item.classList.toggle('is-open', item === group));
});
updateToc();
window.addEventListener('scroll', updateToc, { passive: true });
window.addEventListener('resize', updateToc);
}
})();
</script> <script>
(() => {
const prose = document.querySelector('.post-article .prose');
if (!prose) return;
const collapsedLineCount = 8;
const copyLabels = {
idle: '复制',
done: '已复制',
error: '复制失败',
};
const languageNames = {
bash: 'Bash',
c: 'C',
cpp: 'C++',
cs: 'C#',
css: 'CSS',
html: 'HTML',
js: 'JavaScript',
json: 'JSON',
jsx: 'JSX',
md: 'Markdown',
powershell: 'PowerShell',
py: 'Python',
shell: 'Shell',
sh: 'Shell',
ts: 'TypeScript',
tsx: 'TSX',
txt: 'Text',
yaml: 'YAML',
yml: 'YAML',
};
function getCodeLanguage(pre, code) {
const rawLanguage =
pre.dataset.language ||
code.dataset.language ||
[...pre.classList, ...code.classList]
.find((className) => className.startsWith('language-'))
?.replace(/^language-/, '') ||
'';
const normalized = rawLanguage.trim().toLowerCase();
return languageNames[normalized] || normalized.toUpperCase() || 'Code';
}
function getCodeText(code, lines) {
if (lines.length > 0) return lines.map((line) => line.textContent || '').join('\n');
return code.textContent || '';
}
prose.querySelectorAll('img').forEach((image) => {
if (image.closest('a, .article-header-cover')) return;
const src = image.currentSrc || image.getAttribute('src');
if (!src) return;
const link = document.createElement('a');
link.className = 'fancybox';
link.href = src;
link.dataset.fancybox = 'article-gallery';
link.dataset.caption = image.getAttribute('alt') || '';
image.parentNode?.insertBefore(link, image);
link.appendChild(image);
});
prose.querySelectorAll('table').forEach((table) => {
if (table.closest('.table-scroll')) return;
const wrapper = document.createElement('div');
wrapper.className = 'table-scroll';
table.parentNode?.insertBefore(wrapper, table);
wrapper.appendChild(table);
});
prose.querySelectorAll('.butterfly-hide-inline').forEach((item) => {
const button = item.querySelector('.butterfly-hide-toggle');
if (!button) return;
button.addEventListener('click', () => {
item.classList.toggle('is-open');
});
});
if (window.Fancybox) {
window.Fancybox.bind('[data-fancybox="article-gallery"]', {
compact: false,
dragToClose: true,
Images: {
zoom: true,
},
});
}
async function copyText(text) {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.top = '-9999px';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();
}
prose.querySelectorAll('pre').forEach((pre) => {
if (pre.closest('.code-block')) return;
const code = pre.querySelector('code');
if (!code) return;
const wrapper = document.createElement('div');
wrapper.className = 'code-block';
pre.parentNode?.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const header = document.createElement('div');
header.className = 'code-block-header';
const language = document.createElement('span');
language.className = 'code-block-language';
language.textContent = getCodeLanguage(pre, code);
const toolbar = document.createElement('div');
toolbar.className = 'code-block-toolbar';
const copyButton = document.createElement('button');
copyButton.className = 'code-block-copy';
copyButton.type = 'button';
copyButton.textContent = copyLabels.idle;
copyButton.setAttribute('aria-label', '复制代码');
const toggleButton = document.createElement('button');
toggleButton.className = 'code-block-toggle';
toggleButton.type = 'button';
toggleButton.textContent = '展开';
toggleButton.setAttribute('aria-expanded', 'false');
const expandCue = document.createElement('button');
expandCue.className = 'code-block-expand-cue';
expandCue.type = 'button';
expandCue.setAttribute('aria-label', 'Expand code block');
expandCue.setAttribute('aria-expanded', 'false');
expandCue.innerHTML = '<span aria-hidden="true"></span>';
toolbar.append(copyButton, toggleButton);
header.append(language, toolbar);
wrapper.appendChild(header);
wrapper.appendChild(expandCue);
const codeLines = [...code.querySelectorAll(':scope > .line')];
if (codeLines.length > 0) {
wrapper.classList.add('has-line-numbers');
wrapper.style.setProperty('--line-number-width', `${String(codeLines.length).length + 1}ch`);
codeLines.forEach((line, index) => {
line.dataset.line = String(index + 1);
});
}
const lineCount = codeLines.length || (code.textContent || '').split(/\r?\n/).length;
if (lineCount > collapsedLineCount) wrapper.classList.add('is-collapsible');
copyButton.addEventListener('click', async () => {
try {
await copyText(getCodeText(code, codeLines));
copyButton.textContent = copyLabels.done;
window.setTimeout(() => {
copyButton.textContent = copyLabels.idle;
}, 1400);
} catch {
copyButton.textContent = copyLabels.error;
window.setTimeout(() => {
copyButton.textContent = copyLabels.idle;
}, 1600);
}
});
function toggleExpanded() {
const isExpanded = wrapper.classList.toggle('is-expanded');
toggleButton.textContent = isExpanded ? '收起' : '展开';
toggleButton.setAttribute('aria-expanded', String(isExpanded));
expandCue.setAttribute('aria-expanded', String(isExpanded));
}
toggleButton.addEventListener('click', toggleExpanded);
expandCue.addEventListener('click', toggleExpanded);
});
})();
</script>