From 912c5c4cb63eeb0ecbc33bef6b31bd50d83d6491 Mon Sep 17 00:00:00 2001 From: "web@ppanel" Date: Sat, 27 Dec 2025 11:38:50 +0000 Subject: [PATCH] docs: Add one-click installation script for PPanel with Docker support - Introduced `install-ppanel.sh` script for automated installation of PPanel using Docker Compose. - Updated installation documentation to include one-click deployment options and detailed configuration steps. - Enhanced configuration files for MySQL and Redis with necessary parameters. - Improved Docker Compose setup with health checks and custom network configurations. - Added instructions for firewall configuration and reverse proxy setup for production environments. - Included troubleshooting tips and advanced options for non-interactive installations and proxy environments. --- docs/.vitepress/cache/deps/_metadata.json | 14 +- .../cache/deps/vitepress-theme-teek.js | 194 ++++---- docs/.vitepress/config.mts | 8 + docs/guide/installation.md | 2 +- docs/guide/installation/binary.md | 202 ++++----- docs/guide/installation/docker-compose.md | 153 ++++--- docs/guide/installation/docker-run.md | 131 +++--- docs/guide/installation/one-click.md | 289 ++++++++++++ docs/public/scripts/en/install-docker.sh | 216 +++++++++ docs/public/scripts/en/install-ppanel.sh | 415 +++++++++++++++++ docs/public/scripts/zh/install-docker.sh | 216 +++++++++ docs/public/scripts/zh/install-ppanel.sh | 417 ++++++++++++++++++ docs/zh/guide/installation/binary.md | 200 ++++----- docs/zh/guide/installation/docker-compose.md | 153 ++++--- docs/zh/guide/installation/docker-run.md | 131 +++--- docs/zh/guide/installation/one-click.md | 289 ++++++++++++ 16 files changed, 2466 insertions(+), 564 deletions(-) create mode 100644 docs/guide/installation/one-click.md create mode 100755 docs/public/scripts/en/install-docker.sh create mode 100755 docs/public/scripts/en/install-ppanel.sh create mode 100755 docs/public/scripts/zh/install-docker.sh create mode 100755 docs/public/scripts/zh/install-ppanel.sh create mode 100644 docs/zh/guide/installation/one-click.md diff --git a/docs/.vitepress/cache/deps/_metadata.json b/docs/.vitepress/cache/deps/_metadata.json index c04c2fd..bf71b8a 100644 --- a/docs/.vitepress/cache/deps/_metadata.json +++ b/docs/.vitepress/cache/deps/_metadata.json @@ -1,31 +1,31 @@ { - "hash": "9bf5c878", - "configHash": "c28f4e57", + "hash": "7e859c1e", + "configHash": "1f757845", "lockfileHash": "e3b0c442", - "browserHash": "ae910468", + "browserHash": "1e554b7a", "optimized": { "vue": { "src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", "file": "vue.js", - "fileHash": "4ddaa62e", + "fileHash": "329c7fd1", "needsInterop": false }, "vitepress > @vue/devtools-api": { "src": "../../../../node_modules/@vue/devtools-api/dist/index.js", "file": "vitepress___@vue_devtools-api.js", - "fileHash": "327d077d", + "fileHash": "65813058", "needsInterop": false }, "vitepress > @vueuse/core": { "src": "../../../../node_modules/@vueuse/core/index.mjs", "file": "vitepress___@vueuse_core.js", - "fileHash": "25a885ed", + "fileHash": "015db502", "needsInterop": false }, "vitepress-theme-teek": { "src": "../../../../node_modules/vitepress-theme-teek/es/index.mjs", "file": "vitepress-theme-teek.js", - "fileHash": "bc0f7cc8", + "fileHash": "0cc0cf7f", "needsInterop": false } }, diff --git a/docs/.vitepress/cache/deps/vitepress-theme-teek.js b/docs/.vitepress/cache/deps/vitepress-theme-teek.js index 9513b5c..ab4d197 100644 --- a/docs/.vitepress/cache/deps/vitepress-theme-teek.js +++ b/docs/.vitepress/cache/deps/vitepress-theme-teek.js @@ -1720,7 +1720,7 @@ var useNamespace = (block = "", namespaceOverrides) => { }; // ../node_modules/vitepress-theme-teek/es/composables/useCopyBanner.mjs -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var useCopyBanner = (text = "复制成功,复制和转载请标注本文地址", timeout = 3e3) => { if (!isClient) return; const ns4 = useNamespace("copy-banner"); @@ -6042,7 +6042,7 @@ var version = "1.5.4"; // ../node_modules/vitepress-theme-teek/es/components/theme/ConfigProvider/index.mjs import { useData as useData4, useRoute } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/config/post/helper.mjs var emptyPost = { @@ -6225,19 +6225,19 @@ var useSidebar = () => { // ../node_modules/vitepress-theme-teek/es/components/theme/CataloguePage/src/index.vue2.mjs import { useData as useData6 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/ArticlePage/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/ArticlePage/src/components/DocAsideOutline.vue2.mjs import { useData as useData5, onContentUpdated } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/ArticlePage/src/components/outline.mjs import { getScrollOffset } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var ignoreRE = /\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/; var resolvedHeaders = []; function resolveTitle(theme) { @@ -6371,7 +6371,7 @@ function buildTree(data, min, max) { // ../node_modules/vitepress-theme-teek/es/components/common/ArticlePage/src/components/DocAsideOutlineItem.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_1 = ["href", "title"]; var _sfc_main = defineComponent({ ...{ name: "DocAsideOutlineItem" }, @@ -6558,11 +6558,11 @@ var _sfc_main3 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/CataloguePage/src/CatalogueItem.vue2.mjs import { withBase as withBase2 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/TitleTag/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main4 = defineComponent({ ...{ name: "TitleTag" }, __name: "index", @@ -6795,11 +6795,11 @@ var _sfc_main6 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArchivesPage/src/index.vue2.mjs import { useData as useData7, withBase as withBase3 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleTitle/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/@vue/compiler-core/dist/compiler-core.esm-bundler.js var FRAGMENT = Symbol(true ? `Fragment` : ``); @@ -12996,7 +12996,7 @@ var _sfc_main8 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleOverviewPage/src/index.vue2.mjs import { useData as useData8, withBase as withBase4 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_16 = { key: 0 }; var _hoisted_25 = ["href", "aria-label"]; var _hoisted_34 = ["id"]; @@ -13210,11 +13210,11 @@ var _sfc_main9 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/LoginPage/src/index.vue2.mjs import { useRouter as useRouter2, useData as useData9, withBase as withBase5 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/Icon/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/Icon/src/components/SvgIcon.vue2.mjs var _hoisted_17 = ["innerHTML"]; @@ -15082,7 +15082,7 @@ var _sfc_main14 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/common/Message/src/index.vue2.mjs import { VPBadge } from "vitepress/theme"; import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/Message/src/message.mjs var messageTypes = ["primary", "success", "info", "warning", "error"]; @@ -15530,7 +15530,7 @@ var _sfc_main16 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/LoginPage/src/login.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var getLoginStorageKey = () => { if (!isClient) return { siteLoginKey: "", pagesLoginKey: "", pageLoginKey: "", realmLoginKey: "" }; const ns4 = useNamespace(); @@ -16010,7 +16010,7 @@ var useWatchLogin = () => { // ../node_modules/vitepress-theme-teek/es/components/theme/RiskLinkPage/src/index.vue2.mjs import { useData as useData11 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_112 = ["src"]; var _hoisted_210 = { key: 1 }; var _sfc_main18 = defineComponent({ @@ -16104,7 +16104,7 @@ var _sfc_main18 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/RiskLinkPage/src/useRiskLink.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var useRiskLink = (options = {}) => { const riskLinks = /* @__PURE__ */ new Set(); const cleanups = []; @@ -16152,11 +16152,11 @@ var useRiskLink = (options = {}) => { // ../node_modules/vitepress-theme-teek/es/components/theme/DemoCode/src/index.vue2.mjs import { useData as useData12 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/TransitionCollapse/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main19 = defineComponent({ ...{ name: "TransitionCollapse" }, __name: "index", @@ -16455,15 +16455,15 @@ var music = ` inject(paginationKey, {}); @@ -16722,7 +16722,7 @@ var _sfc_main24 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/common/Pagination/src/components/total.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_117 = ["disabled"]; var _sfc_main25 = defineComponent({ ...{ name: "PaginationTotal" }, @@ -16747,7 +16747,7 @@ var _sfc_main25 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/common/Pagination/src/components/pager.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_118 = ["aria-current", "aria-label", "tabindex"]; var _hoisted_215 = ["tabindex", "aria-label"]; var _hoisted_37 = ["innerHTML"]; @@ -17111,11 +17111,11 @@ var pageNumKey = "pageNum"; // ../node_modules/vitepress-theme-teek/es/components/theme/HomePostList/src/HomePostItem.vue2.mjs import { withBase as withBase7 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleInfo/src/index.vue2.mjs import { useData as useData13, useRoute as useRoute2, withBase as withBase6 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_119 = ["aria-label"]; var _hoisted_216 = ["aria-label"]; var _hoisted_38 = ["title", "href", "target", "aria-label"]; @@ -17453,7 +17453,7 @@ var _sfc_main29 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomePostList/src/HomePostItemCard.vue2.mjs import { withBase as withBase8 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_121 = ["href", "alt"]; var _hoisted_218 = ["src"]; var _hoisted_310 = ["href", "aria-label"]; @@ -17782,11 +17782,11 @@ var _sfc_main31 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeBanner/src/index.vue2.mjs import { useData as useData17 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/HomeBanner/src/HomeBannerBgPure.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_123 = ["aria-label"]; var _sfc_main32 = defineComponent({ ...{ name: "HomeBannerBgPure" }, @@ -17813,7 +17813,7 @@ var _sfc_main32 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeBanner/src/HomeBannerBgImage.vue2.mjs import { withBase as withBase9 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_124 = ["aria-label"]; var _hoisted_220 = ["aria-label"]; var _sfc_main33 = defineComponent({ @@ -17896,7 +17896,7 @@ var _sfc_main33 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeBanner/src/HomeBannerContent.vue2.mjs import { useData as useData15 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_125 = ["aria-label"]; var _hoisted_221 = ["aria-label"]; var _hoisted_311 = ["aria-label"]; @@ -18031,7 +18031,7 @@ var _sfc_main34 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeBanner/src/HomeBannerFeature.vue2.mjs import { useData as useData16, withBase as withBase10 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_126 = ["aria-labelledby"]; var _hoisted_222 = ["src", "alt", "aria-label"]; var _hoisted_312 = ["id"]; @@ -18140,7 +18140,7 @@ var _sfc_main35 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeBanner/src/HomeBannerWaves.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main36 = defineComponent({ ...{ name: "HomeBannerWaves" }, __name: "HomeBannerWaves", @@ -18365,15 +18365,15 @@ var _sfc_main37 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeCardList/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/HomeMyCard/src/index.vue2.mjs import { withBase as withBase12 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/PageCard/src/index.vue2.mjs import { withBase as withBase11 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_128 = ["aria-label"]; var _hoisted_223 = ["href", "target", "aria-label"]; var _hoisted_313 = ["innerHTML"]; @@ -18528,7 +18528,7 @@ var _sfc_main38 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/common/Avatar/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_129 = ["src", "alt", "srcSet"]; var _hoisted_224 = { key: 2 }; var _sfc_main39 = defineComponent({ @@ -18772,7 +18772,7 @@ var _sfc_main40 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeTopArticleCard/src/index.vue2.mjs import { useRouter as useRouter4, withBase as withBase13 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_131 = ["aria-label"]; var _hoisted_226 = ["href"]; var _hoisted_315 = { class: "date" }; @@ -18923,7 +18923,7 @@ var _sfc_main41 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeCategoryCard/src/index.vue2.mjs import { useRouter as useRouter5, withBase as withBase14 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/Home/src/home.mjs var postDataUpdateSymbol = Symbol("postDataUpdate"); @@ -19088,7 +19088,7 @@ var _sfc_main42 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeTagCard/src/index.vue2.mjs import { useRouter as useRouter6, withBase as withBase15 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_133 = ["onClick", "aria-label"]; var _hoisted_228 = { class: "num" }; var _hoisted_317 = ["href", "aria-label"]; @@ -19255,7 +19255,7 @@ var _sfc_main43 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeFriendLinkCard/src/index.vue2.mjs import { useRouter as useRouter7 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/HomeFriendLinkCard/src/ItemInfo.vue2.mjs import { withBase as withBase16 } from "vitepress"; @@ -19465,7 +19465,7 @@ var _sfc_main45 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeDocAnalysisCard/src/index.vue2.mjs import { useData as useData18 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_136 = ["innerHTML"]; var _hoisted_230 = ["innerHTML"]; var _sfc_main46 = defineComponent({ @@ -19968,7 +19968,7 @@ var _sfc_main48 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeMyCard/src/HomeMyCardScreen.vue2.mjs import { withBase as withBase17 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_138 = ["src", "alt"]; var _hoisted_232 = ["aria-label"]; var _hoisted_320 = { class: "name" }; @@ -20045,7 +20045,7 @@ var _sfc_main49 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/BodyBgImage/src/index.vue2.mjs import { withBase as withBase18 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_139 = { key: 0, class: "mask" @@ -20118,7 +20118,7 @@ var _sfc_main50 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/FooterGroup/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_140 = ["name", "href", "title", "target", "aria-label", "aria-describedby"]; var _hoisted_233 = { class: "sle" }; var _sfc_main51 = defineComponent({ @@ -20230,7 +20230,7 @@ var _sfc_main51 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/FooterInfo/src/index.vue2.mjs import { withBase as withBase19 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/static/img/securityRecord.png.mjs var securityRecordImg = "data:image/png;base64,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"; @@ -20419,11 +20419,11 @@ var _sfc_main52 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleImagePreview/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/ImageViewer/src/ImageViewer.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/FocusTrap/src/useEscapeKeydown.mjs var registeredEscapeHandlers = []; @@ -21382,22 +21382,22 @@ var _sfc_main56 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleAnalyze/src/index.vue2.mjs import { useData as useData20 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleBreadcrumb/src/index.vue2.mjs import { useData as useData19, withBase as withBase20 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/Breadcrumb/src/Breadcrumb.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/Breadcrumb/src/breadcrumb.mjs var breadcrumbKey = Symbol("breadcrumbKey"); // ../node_modules/vitepress-theme-teek/es/components/common/Breadcrumb/src/namespace.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var ns = useNamespace("breadcrumb"); // ../node_modules/vitepress-theme-teek/es/components/common/Breadcrumb/src/Breadcrumb.vue2.mjs @@ -21752,7 +21752,7 @@ var _sfc_main60 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleShare/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_146 = ["aria-label"]; var _hoisted_237 = { key: 0, @@ -21828,7 +21828,7 @@ var _sfc_main61 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleUpdate/src/index.vue2.mjs import { useRoute as useRoute4, useData as useData21, withBase as withBase21 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_147 = ["href", "aria-label"]; var _hoisted_238 = { key: 1 }; var _hoisted_324 = ["href", "aria-label"]; @@ -21955,7 +21955,7 @@ var _sfc_main62 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleHeadingHighlight/src/index.vue2.mjs import { useRoute as useRoute5 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main63 = defineComponent({ __name: "index", setup(__props) { @@ -21993,7 +21993,7 @@ var _sfc_main63 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticlePageStyle/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main64 = defineComponent({ __name: "index", setup(__props) { @@ -22022,7 +22022,7 @@ var _sfc_main64 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleAppreciation/src/AsideBottomAppreciation.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_148 = ["aria-label"]; var _hoisted_239 = ["innerHTML"]; var _hoisted_325 = ["innerHTML"]; @@ -22054,7 +22054,7 @@ var _sfc_main65 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleAppreciation/src/DocAfterAppreciation.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_149 = ["aria-label"]; var _hoisted_240 = ["innerHTML", "aria-expanded", "aria-controls"]; var _hoisted_326 = ["aria-expanded", "aria-controls"]; @@ -22146,11 +22146,11 @@ var _sfc_main66 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleAppreciation/src/DocAfterAppreciationPopper.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/Popover/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/Popover/src/useFocusTrap.mjs var useFocusTrap = (visible, emit) => { @@ -22466,7 +22466,7 @@ var _sfc_main68 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/CommentTwikoo/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/CommentTwikoo/src/twikoo.mjs var twikooContext = Symbol("twikoo"); @@ -22583,7 +22583,7 @@ var _sfc_main69 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/CommentArtalk/src/index.vue2.mjs import { useData as useData22 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/CommentArtalk/src/artalk.mjs var artalkContext = Symbol("artalk"); @@ -22704,7 +22704,7 @@ var _sfc_main70 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/CommentGiscus/src/index.vue2.mjs import { useData as useData23 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/CommentGiscus/src/giscus.mjs var giscusContext = Symbol("giscus"); @@ -22809,7 +22809,7 @@ var _sfc_main71 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/CommentWaline/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/CommentWaline/src/waline.mjs var walineContext = Symbol("waline"); @@ -22880,7 +22880,7 @@ var _sfc_main72 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/CodeBlockToggle/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/CodeBlockToggle/src/Overlay.vue2.mjs var _sfc_main73 = defineComponent({ @@ -23037,16 +23037,16 @@ var _sfc_main74 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/RightBottomButton/src/index.vue2.mjs import { useData as useData25 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/RightBottomButton/src/namespace.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var ns2 = useNamespace("right-bottom-button"); // ../node_modules/vitepress-theme-teek/es/components/theme/RightBottomButton/src/BackTop.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_154 = ["title", "aria-label", "aria-valuenow"]; var _hoisted_242 = { key: 0, @@ -23138,7 +23138,7 @@ var _sfc_main75 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/RightBottomButton/src/ToComment.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_155 = ["title", "aria-label"]; var _sfc_main76 = defineComponent({ ...{ name: "ToComment" }, @@ -23210,7 +23210,7 @@ var _sfc_main76 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/RightBottomButton/src/ThemeColor.vue2.mjs import { useData as useData24 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/themeEnhance.mjs var LayoutMode = ((LayoutMode2) => { @@ -23255,7 +23255,7 @@ var themeColorAttribute = "theme-color"; // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/namespace.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var ns3 = useNamespace("theme-enhance"); var transitionName = ns3.join("theme-enhance-slide"); var pageMaxWidthVar = ns3.cssVarName("page-max-width"); @@ -23270,7 +23270,7 @@ var spotlightStyleStorageKey = ns3.storageKey("spotlightStyle"); // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/useThemeColorList.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var useThemeColorList = () => { if (!isClient) return; const { getTeekConfigRef } = useTeekConfig(); @@ -23548,7 +23548,7 @@ var _sfc_main78 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/Notice/src/index.vue2.mjs import { useData as useData26 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_157 = ["aria-label"]; var _hoisted_244 = ["aria-label"]; var _hoisted_328 = ["aria-label"]; @@ -23778,19 +23778,19 @@ var _sfc_main79 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/LayoutSwitch.vue2.mjs import { useData as useData27 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/Segmented/src/Segmented.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/Segmented/src/SegmentedItem.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_158 = ["title", "disabled"]; var _hoisted_245 = ["value", "name", "disabled", "checked", "aria-checked"]; var _hoisted_329 = { key: 1 }; @@ -23903,7 +23903,7 @@ var _sfc_main81 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/useAnimated.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var useAnimated = (delay = 1e3, immediate = false) => { let timer = null; const stop = () => { @@ -24022,7 +24022,7 @@ var _sfc_main83 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/components/BorderHighlight.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main84 = defineComponent({ ...{ name: "BorderHighlight" }, __name: "BorderHighlight", @@ -24333,11 +24333,11 @@ var _sfc_main86 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/LayoutPageWidthSlide.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/common/InputSlide/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_161 = ["name", "min", "max", "disabled", "step"]; var _sfc_main87 = defineComponent({ ...{ name: "InputSlide" }, @@ -24575,7 +24575,7 @@ var _sfc_main88 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/LayoutDocWidthSlide.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main89 = defineComponent({ ...{ name: "LayoutDocWidthSlide" }, __name: "LayoutDocWidthSlide", @@ -24662,7 +24662,7 @@ var _sfc_main89 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/ThemeColor.vue2.mjs import { useData as useData28 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/components/Switch.vue2.mjs var _sfc_main90 = defineComponent({ @@ -24883,11 +24883,11 @@ var _sfc_main91 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/Spotlight.vue2.mjs import { useData as useData29 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/components/SpotlightHover.vue2.mjs import { useRoute as useRoute6 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main92 = defineComponent({ ...{ name: "SpotlightHover" }, __name: "SpotlightHover", @@ -25103,7 +25103,7 @@ var _sfc_main93 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/ThemeEnhance/src/SpotlightStyle.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main94 = defineComponent({ ...{ name: "SpotlightStyle" }, __name: "SpotlightStyle", @@ -25257,7 +25257,7 @@ var _sfc_main95 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/common/VpContainer/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_163 = { key: 0, class: "custom-block-title" @@ -25316,7 +25316,7 @@ var _sfc_main96 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/SidebarTrigger/src/index.vue2.mjs import "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_164 = ["title", "aria-label"]; var _sfc_main97 = defineComponent({ __name: "index", @@ -25376,7 +25376,7 @@ var _sfc_main97 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/HomeFeature/src/index.vue2.mjs import { withBase as withBase22 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_165 = ["src"]; var _hoisted_249 = ["innerHTML"]; var _hoisted_333 = ["innerHTML"]; @@ -25645,7 +25645,7 @@ var _sfc_main98 = defineComponent({ // ../node_modules/vitepress-theme-teek/es/components/theme/RouteLoading/src/index.vue.mjs import { onContentUpdated as onContentUpdated2 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _sfc_main99 = { __name: "index", props: { @@ -25761,7 +25761,7 @@ var _sfc_main99 = { // ../node_modules/vitepress-theme-teek/es/components/theme/ArticleBanner/src/index.vue2.mjs import { useData as useData30, useRoute as useRoute7, withBase as withBase23 } from "vitepress"; -import "/Users/draco/Desktop/ppanel-web/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; +import "/workspaces/frontend/node_modules/vitepress-theme-teek/theme-chalk/tk-copy-banner.css"; var _hoisted_166 = ["src"]; var _hoisted_250 = { key: 0, diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 965c788..adaf18b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -67,6 +67,10 @@ export default defineConfig({ text: "Installation", items: [ { text: "Overview", link: "/guide/installation/" }, + { + text: "One-Click Deployment", + link: "/guide/installation/one-click", + }, { text: "Docker Compose", link: "/guide/installation/docker-compose", @@ -250,6 +254,10 @@ export default defineConfig({ text: "安装部署", items: [ { text: "概览", link: "/zh/guide/installation/" }, + { + text: "一键部署", + link: "/zh/guide/installation/one-click", + }, { text: "Docker Compose", link: "/zh/guide/installation/docker-compose", diff --git a/docs/guide/installation.md b/docs/guide/installation.md index d9df9cc..735fea6 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -209,7 +209,7 @@ docker compose ps After successful installation, you can access: - **User Panel**: `http://your-server-ip:8080` -- **Admin Panel**: `http://your-server-ip:8080/admin` +- **Admin Panel**: `http://your-server-ip:8080/admin/` ::: warning Default Credentials Please change the default admin password immediately after first login for security. diff --git a/docs/guide/installation/binary.md b/docs/guide/installation/binary.md index 1095cd3..f5ad4cb 100644 --- a/docs/guide/installation/binary.md +++ b/docs/guide/installation/binary.md @@ -23,19 +23,29 @@ uname -m Visit the [GitHub Releases](https://github.com/perfect-panel/ppanel/releases) page or download directly: +::: tip Installation Directory +You can install PPanel in any directory. This guide uses `/opt/ppanel` as an example. If you choose a different directory, adjust the paths in subsequent commands accordingly. +::: + ```bash -# Create installation directory +# Create installation directory (customizable) sudo mkdir -p /opt/ppanel cd /opt/ppanel # Download for Linux amd64 -wget https://github.com/perfect-panel/ppanel/releases/latest/download/ppanel-linux-amd64.tar.gz +wget https://github.com/perfect-panel/ppanel/releases/latest/download/gateway-linux-amd64.tar.gz # Or for Linux arm64 -# wget https://github.com/perfect-panel/ppanel/releases/latest/download/ppanel-linux-arm64.tar.gz +# wget https://github.com/perfect-panel/ppanel/releases/latest/download/gateway-linux-arm64.tar.gz + +# Or for macOS amd64 +# wget https://github.com/perfect-panel/ppanel/releases/latest/download/gateway-darwin-amd64.tar.gz + +# Or for macOS arm64 (Apple Silicon) +# wget https://github.com/perfect-panel/ppanel/releases/latest/download/gateway-darwin-arm64.tar.gz # Extract -tar -xzf ppanel-linux-amd64.tar.gz +tar -xzf gateway-linux-amd64.tar.gz # Verify extracted files ls -la @@ -44,8 +54,7 @@ ls -la Expected files: ``` /opt/ppanel/ -├── ppanel-server # Main server binary -├── gateway # Gateway binary +├── gateway # Gateway executable └── etc/ # Configuration directory └── ppanel.yaml # Configuration file ``` @@ -55,52 +64,92 @@ Expected files: ### Step 1: Prepare Configuration ```bash -# Copy sample configuration -sudo cp etc/ppanel.yaml etc/ppanel.yaml.backup - # Edit configuration -sudo nano etc/ppanel.yaml +sudo nano /opt/ppanel/etc/ppanel.yaml ``` -**Basic Configuration Example:** +**Configuration Example:** + +::: tip Relative Paths +Paths in the configuration (such as `Path`, `logs`, etc.) support relative paths. Relative paths are relative to the program's working directory (WorkingDirectory), which is `/opt/ppanel` in the systemd service. +::: ```yaml -server: - host: 0.0.0.0 - port: 8080 - mode: release # debug, release, or test +Host: 0.0.0.0 +Port: 8080 +TLS: + Enable: false + CertFile: "" + KeyFile: "" +Debug: false -database: - type: sqlite - path: /opt/ppanel/data/ppanel.db - # For MySQL/PostgreSQL: - # type: mysql - # host: localhost - # port: 3306 - # user: ppanel - # password: your_password - # database: ppanel +Static: + Admin: + Enabled: true + Prefix: /admin + Path: ./static/admin + User: + Enabled: true + Prefix: / + Path: ./static/user -log: - level: info # debug, info, warn, error - path: /opt/ppanel/logs +JwtAuth: + AccessSecret: your-secret-key-change-this + AccessExpire: 604800 -gateway: - port: 8080 - timeout: 30s +Logger: + ServiceName: ApiService + Mode: console + Encoding: plain + TimeFormat: "2006-01-02 15:04:05.000" + Path: logs + Level: info + MaxContentLength: 0 + Compress: false + Stat: true + KeepDays: 0 + StackCooldownMillis: 100 + MaxBackups: 0 + MaxSize: 0 + Rotation: daily + FileTimeFormat: 2006-01-02T15:04:05.000Z07:00 + +MySQL: + Addr: localhost:3306 + Username: your-username + Password: your-password + Dbname: ppanel + Config: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai + MaxIdleConns: 10 + MaxOpenConns: 10 + SlowThreshold: 1000 + +Redis: + Host: localhost:6379 + Pass: your-redis-password + DB: 0 ``` +::: warning Required Configuration +**MySQL and Redis are required.** Please configure the following before deployment: +- `JwtAuth.AccessSecret` - Use a strong random secret (required) +- `MySQL.*` - Configure your MySQL database connection (required) +- `Redis.*` - Configure your Redis connection (required) +::: + ### Step 2: Create Required Directories ```bash # Create data and log directories sudo mkdir -p /opt/ppanel/data sudo mkdir -p /opt/ppanel/logs +sudo mkdir -p /opt/ppanel/static # Set proper permissions sudo chmod 755 /opt/ppanel sudo chmod 700 /opt/ppanel/data sudo chmod 755 /opt/ppanel/logs +sudo chmod 755 /opt/ppanel/static ``` ## Running the Service @@ -110,16 +159,12 @@ sudo chmod 755 /opt/ppanel/logs For quick testing: ```bash -# Make binaries executable -sudo chmod +x /opt/ppanel/ppanel-server +# Make binary executable sudo chmod +x /opt/ppanel/gateway -# Run server directly +# Run directly cd /opt/ppanel -sudo ./ppanel-server - -# In another terminal, run gateway (if separate) -# sudo ./gateway +sudo ./gateway ``` Press `Ctrl+C` to stop. @@ -147,7 +192,7 @@ Wants=network-online.target Type=simple User=root WorkingDirectory=/opt/ppanel -ExecStart=/opt/ppanel/ppanel-server +ExecStart=/opt/ppanel/gateway Restart=always RestartSec=10 @@ -262,7 +307,7 @@ ps aux | grep ppanel ### Access the Application - **User Panel**: `http://your-server-ip:8080` -- **Admin Panel**: `http://your-server-ip:8080/admin` +- **Admin Panel**: `http://your-server-ip:8080/admin/` ### Configure Firewall @@ -312,70 +357,11 @@ sudo systemctl reload nginx ## Upgrading -### Backup Before Upgrade +Upgrade PPanel directly from the **Admin Dashboard**. On the dashboard homepage, you can check for new versions and upgrade with one click. -```bash -# Stop service -sudo systemctl stop ppanel - -# Backup current version -sudo cp -r /opt/ppanel /opt/ppanel-backup-$(date +%Y%m%d) - -# Backup database -sudo cp /opt/ppanel/data/ppanel.db /opt/ppanel/data/ppanel.db.backup-$(date +%Y%m%d) - -# Backup configuration -sudo cp /opt/ppanel/etc/ppanel.yaml /opt/ppanel/etc/ppanel.yaml.backup-$(date +%Y%m%d) -``` - -### Download and Install New Version - -```bash -# Download new version -cd /tmp -wget https://github.com/perfect-panel/ppanel/releases/latest/download/ppanel-linux-amd64.tar.gz - -# Extract to temporary location -mkdir ppanel-new -tar -xzf ppanel-linux-amd64.tar.gz -C ppanel-new - -# Backup old binaries -sudo mv /opt/ppanel/ppanel-server /opt/ppanel/ppanel-server.old -sudo mv /opt/ppanel/gateway /opt/ppanel/gateway.old - -# Install new binaries -sudo cp ppanel-new/ppanel-server /opt/ppanel/ -sudo cp ppanel-new/gateway /opt/ppanel/ - -# Set permissions -sudo chmod +x /opt/ppanel/ppanel-server -sudo chmod +x /opt/ppanel/gateway - -# Start service -sudo systemctl start ppanel - -# Check status -sudo systemctl status ppanel -``` - -### Rollback - -If upgrade fails: - -```bash -# Stop service -sudo systemctl stop ppanel - -# Restore old binaries -sudo mv /opt/ppanel/ppanel-server.old /opt/ppanel/ppanel-server -sudo mv /opt/ppanel/gateway.old /opt/ppanel/gateway - -# Restore database (if needed) -sudo cp /opt/ppanel/data/ppanel.db.backup-YYYYMMDD /opt/ppanel/data/ppanel.db - -# Start service -sudo systemctl start ppanel -``` +::: tip +The system will automatically handle the upgrade process, including downloading the new binary and restarting the service. +::: ## Troubleshooting @@ -413,14 +399,14 @@ sudo systemctl restart ppanel ```bash # Check architecture compatibility uname -m -file /opt/ppanel/ppanel-server +file /opt/ppanel/gateway # Check if executable -ls -la /opt/ppanel/ppanel-server -sudo chmod +x /opt/ppanel/ppanel-server +ls -la /opt/ppanel/gateway +sudo chmod +x /opt/ppanel/gateway # Check for missing libraries (should be none for static binary) -ldd /opt/ppanel/ppanel-server +ldd /opt/ppanel/gateway ``` ### High Memory Usage @@ -497,7 +483,7 @@ sudo nano /etc/systemd/system/ppanel.service # Change: User=ppanel # If binding to port < 1024, grant capability -sudo setcap 'cap_net_bind_service=+ep' /opt/ppanel/ppanel-server +sudo setcap 'cap_net_bind_service=+ep' /opt/ppanel/gateway sudo systemctl daemon-reload sudo systemctl restart ppanel diff --git a/docs/guide/installation/docker-compose.md b/docs/guide/installation/docker-compose.md index 735cf26..9e69296 100644 --- a/docs/guide/installation/docker-compose.md +++ b/docs/guide/installation/docker-compose.md @@ -77,17 +77,17 @@ Create a `docker-compose.yml` file with the following content: version: '3.8' services: - ppanel: + ppanel-service: image: ppanel/ppanel:latest - container_name: ppanel + container_name: ppanel-service + restart: always ports: - "8080:8080" volumes: - - ./ppanel-config:/app/etc:ro - - ppanel-data:/app/data - restart: unless-stopped - environment: - - TZ=UTC + - ./config:/app/etc:ro + - ./web:/app/static + networks: + - ppanel-net healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"] interval: 30s @@ -95,43 +95,94 @@ services: retries: 3 start_period: 40s -volumes: - ppanel-data: - driver: local +networks: + ppanel-net: + driver: bridge ``` **Configuration Explanation:** - **image**: Docker image to use (latest or specific version like `v0.1.2`) -- **ports**: Map container port 8080 to host port 8080 +- **container_name**: Set a custom container name +- **ports**: Map container port 8080 to host port 8080 (change host port if needed) - **volumes**: - - `./ppanel-config:/app/etc:ro` - Configuration directory (read-only) - - `ppanel-data:/app/data` - Persistent data storage -- **restart**: Auto-restart policy -- **environment**: Set timezone (change to your timezone like `Asia/Shanghai`) + - `./config:/app/etc:ro` - Configuration directory (read-only) + - `./web:/app/static` - Static files directory (admin and user frontend) +- **networks**: Create a custom network for service isolation +- **restart**: Auto-restart policy (always restart on failures) - **healthcheck**: Monitor service health ### Step 3: Prepare Configuration ```bash # Create configuration directory -mkdir -p ppanel-config +mkdir -p config # Create configuration file -cat > ppanel-config/ppanel.yaml < config/ppanel.yaml < Container port 8080 +services: + ppanel-service: + ports: + - "3000:8080" # Host port 3000 -> Container port 8080 ``` ### Multiple Instances @@ -403,10 +424,10 @@ sudo lsof -i :8080 ```bash # Fix configuration directory permissions -sudo chown -R $USER:$USER ppanel-config/ +sudo chown -R $USER:$USER config/ # Make sure files are readable -chmod 644 ppanel-config/ppanel.yaml +chmod 644 config/ppanel.yaml ``` ### Cannot Access from Outside diff --git a/docs/guide/installation/docker-run.md b/docs/guide/installation/docker-run.md index e5cc116..1b75030 100644 --- a/docs/guide/installation/docker-run.md +++ b/docs/guide/installation/docker-run.md @@ -67,53 +67,106 @@ mkdir -p ~/ppanel-config # Create configuration file cat > ~/ppanel-config/ppanel.yaml < /dev/null; then + DOCKER_VERSION=$(docker --version | cut -d ' ' -f3 | cut -d ',' -f1) + log_warn "Docker is already installed (Version: $DOCKER_VERSION)" + read -p "Do you want to reinstall? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "Skipping Docker installation" + return 0 + fi + fi + return 1 +} + +# Install Docker - Ubuntu/Debian +install_docker_debian() { + log_info "Installing Docker on Ubuntu/Debian..." + + # Update package index + apt-get update + + # Install dependencies + apt-get install -y \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + + # Add Docker's official GPG key + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/$OS/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + chmod a+r /etc/apt/keyrings/docker.gpg + + # Set up repository + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$OS \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + + # Install Docker Engine + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + log_info "Docker installation completed" +} + +# Install Docker - CentOS/RHEL +install_docker_centos() { + log_info "Installing Docker on CentOS/RHEL..." + + # Install yum-utils + yum install -y yum-utils + + # Add Docker repository + yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + + # Install Docker Engine + yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + log_info "Docker installation completed" +} + +# Start Docker service +start_docker() { + log_info "Starting Docker service..." + + systemctl start docker + systemctl enable docker + + log_info "Docker service started and enabled at boot" +} + +# Configure Docker group (optional) +configure_docker_group() { + log_info "Configuring Docker group..." + + if ! getent group docker > /dev/null; then + groupadd docker + fi + + if [[ -n "$SUDO_USER" ]]; then + usermod -aG docker "$SUDO_USER" + log_info "User $SUDO_USER has been added to docker group" + log_warn "Please log out and log back in for group permissions to take effect" + fi +} + +# Verify installation +verify_installation() { + log_info "Verifying Docker installation..." + + # Check Docker version + if docker --version &> /dev/null; then + DOCKER_VERSION=$(docker --version) + log_info "✓ $DOCKER_VERSION" + else + log_error "Docker installation failed" + exit 1 + fi + + # Check Docker Compose version + if docker compose version &> /dev/null; then + COMPOSE_VERSION=$(docker compose version) + log_info "✓ $COMPOSE_VERSION" + else + log_error "Docker Compose installation failed" + exit 1 + fi + + # Run test container + log_info "Running test container..." + if docker run --rm hello-world &> /dev/null; then + log_info "✓ Docker is working correctly" + else + log_error "Docker test failed" + exit 1 + fi +} + +# Main function +main() { + echo "========================================" + echo " Docker & Docker Compose Installer" + echo "========================================" + echo "" + + check_root + detect_os + + if check_docker_installed; then + verify_installation + exit 0 + fi + + case $OS in + ubuntu|debian) + install_docker_debian + ;; + centos|rhel|rocky|almalinux) + install_docker_centos + ;; + *) + log_error "Unsupported operating system: $OS" + log_info "Supported systems: Ubuntu, Debian, CentOS, RHEL, Rocky Linux, AlmaLinux" + exit 1 + ;; + esac + + start_docker + configure_docker_group + verify_installation + + echo "" + log_info "========================================" + log_info "Docker and Docker Compose installed successfully!" + log_info "========================================" + log_info "Docker version: $(docker --version)" + log_info "Docker Compose version: $(docker compose version)" + echo "" + log_info "Next step: Install PPanel" + log_info "Run: curl -fsSL https://ppanel.dev/scripts/en/install-ppanel.sh | bash" +} + +main "$@" diff --git a/docs/public/scripts/en/install-ppanel.sh b/docs/public/scripts/en/install-ppanel.sh new file mode 100755 index 0000000..713da45 --- /dev/null +++ b/docs/public/scripts/en/install-ppanel.sh @@ -0,0 +1,415 @@ +#!/bin/bash + +####################################################### +# PPanel One-Click Installation Script +# Support: Docker Compose Deployment +# Usage: curl -fsSL https://ppanel.dev/scripts/en/install-ppanel.sh | bash +####################################################### + +set -e + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 配置变量 +INSTALL_DIR="${INSTALL_DIR:-$(pwd)/ppanel}" +DOCKER_IMAGE="ppanel/ppanel:latest" +CONTAINER_NAME="ppanel-service" +HOST_PORT="${HOST_PORT:-8080}" + +# 日志函数 +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_step() { + echo -e "${BLUE}[STEP]${NC} $1" +} + +# Check if Docker is installed +check_docker() { + if ! command -v docker &> /dev/null; then + log_error "Docker is not installed" + log_info "Please install Docker first:" + log_info "curl -fsSL https://ppanel.dev/scripts/en/install-docker.sh | sudo bash" + exit 1 + fi + + if ! docker compose version &> /dev/null; then + log_error "Docker Compose is not installed" + log_info "Please install Docker Compose first" + exit 1 + fi + + log_info "✓ Docker installed ($(docker --version))" + log_info "✓ Docker Compose installed ($(docker compose version --short))" +} + +# Check if port is in use +check_port() { + if command -v netstat &> /dev/null; then + if netstat -tuln | grep -q ":$HOST_PORT "; then + log_error "Port $HOST_PORT is already in use" + log_info "Please set another port: export HOST_PORT=8081" + exit 1 + fi + elif command -v ss &> /dev/null; then + if ss -tuln | grep -q ":$HOST_PORT "; then + log_error "Port $HOST_PORT is already in use" + log_info "Please set another port: export HOST_PORT=8081" + exit 1 + fi + fi + log_info "✓ Port $HOST_PORT is available" +} + +# Create installation directories +create_directories() { + log_step "Creating installation directories..." + + mkdir -p "$INSTALL_DIR"/config + # Set permissions to ensure container can read configuration files + chmod -R 755 "$INSTALL_DIR" + + log_info "✓ Directories created: $INSTALL_DIR" +} + +# 生成随机密钥 +generate_secret() { + if command -v openssl &> /dev/null; then + openssl rand -hex 32 + else + cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 64 | head -n 1 + fi +} + +# Get user input +get_user_input() { + log_step "Configuring PPanel..." + echo "" + + # MySQL configuration + MYSQL_USER="ppanel" + MYSQL_PASSWORD=$(generate_secret | cut -c1-16) + MYSQL_DB="ppanel" + MYSQL_ROOT_PASSWORD=$(generate_secret | cut -c1-16) + + log_info "✓ MySQL password generated" + + # Redis configuration + REDIS_PASS=$(generate_secret | cut -c1-16) + + log_info "✓ Redis password generated" + + # Generate JWT secret + JWT_SECRET=$(generate_secret) + log_info "✓ JWT secret generated" +} + +# Create configuration file +create_config() { + log_step "Creating configuration file..." + + cat > "$INSTALL_DIR/config/ppanel.yaml" < "$INSTALL_DIR/docker-compose.yml" <<'COMPOSE_EOF' +services: + mysql: + image: mysql:8.0 + container_name: ppanel-mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DB} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + TZ: Asia/Shanghai + volumes: + - mysql:/var/lib/mysql + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + healthcheck: + test: [ + "CMD-SHELL", + "mysqladmin ping -h localhost -u $$MYSQL_USER -p$$MYSQL_PASSWORD || exit 1" + ] + interval: 5s + timeout: 3s + retries: 10 + start_period: 30s + + redis: + image: redis:7-alpine + container_name: ppanel-redis + restart: always + command: redis-server --requirepass ${REDIS_PASS} --appendonly yes + volumes: + - redis:/data + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 10s + timeout: 3s + retries: 5 + + ppanel-service: + image: ${DOCKER_IMAGE} + container_name: ${CONTAINER_NAME} + restart: always + ports: + - "${HOST_PORT}:8080" + volumes: + - ./config:/app/etc:ro + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + +volumes: + mysql: + redis: +COMPOSE_EOF + + log_info "✓ Docker Compose configuration created" + + # Create .env file + cat > "$INSTALL_DIR/.env" < /dev/null; then + if curl -s "http://localhost:$HOST_PORT" &> /dev/null; then + log_info "✓ Service is ready" + return 0 + fi + elif command -v wget &> /dev/null; then + if wget --quiet --tries=1 --spider "http://localhost:$HOST_PORT" &> /dev/null; then + log_info "✓ Service is ready" + return 0 + fi + else + # If neither curl nor wget is available, check container health directly + if docker inspect ppanel-service --format='{{.State.Health.Status}}' 2>/dev/null | grep -q "healthy"; then + log_info "✓ Service is ready" + return 0 + fi + fi + + attempt=$((attempt + 1)) + echo -n "." + sleep 2 + done + + echo "" + log_warn "Service startup timeout, please check logs" + log_info "View logs: cd $INSTALL_DIR && docker compose logs -f" +} + +# Show access information +show_access_info() { + local internal_ip + local public_ip + + # Get internal IP + internal_ip=$(hostname -I | awk '{print $1}') + + # Get public IP + public_ip=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || curl -s --max-time 5 https://ifconfig.me 2>/dev/null || echo "Unable to get") + + echo "" + echo "========================================" + log_info "PPanel installation completed!" + echo "========================================" + echo "" + log_info "Access URLs:" + echo " Internal Access (LAN):" + echo " User Panel: http://$internal_ip:$HOST_PORT" + echo " Admin Panel: http://$internal_ip:$HOST_PORT/admin/" + echo "" + if [[ "$public_ip" != "Unable to get" ]]; then + echo " Public Access (Internet):" + echo " User Panel: http://$public_ip:$HOST_PORT" + echo " Admin Panel: http://$public_ip:$HOST_PORT/admin/" + echo "" + log_warn "Note: Public access requires firewall and router port forwarding configuration" + else + log_warn "Unable to get public IP, please configure manually for public access" + fi + echo "" + log_info "Database Information:" + echo " MySQL (Container Network):" + echo " Address: mysql:3306" + echo " User: $MYSQL_USER" + echo " Password: $MYSQL_PASSWORD" + echo " Database: $MYSQL_DB" + echo " Root Password: $MYSQL_ROOT_PASSWORD" + echo "" + echo " Redis (Container Network):" + echo " Address: redis:6379" + echo " Password: $REDIS_PASS" + echo "" + log_warn "Note: MySQL and Redis are only accessible within the container network" + echo " To access from host, edit docker-compose.yml to add port mappings" + echo "" + log_info "Installation directory: $INSTALL_DIR" + echo "" + log_info "Common commands:" + echo " Check status: cd $INSTALL_DIR && docker compose ps" + echo " View logs: cd $INSTALL_DIR && docker compose logs -f" + echo " Restart: cd $INSTALL_DIR && docker compose restart" + echo " Stop: cd $INSTALL_DIR && docker compose stop" + echo " Start: cd $INSTALL_DIR && docker compose start" + echo "" + log_warn "Important reminders:" + echo " 1. Please keep database password information safe" + echo " 2. Please login to admin panel and change the default password" + echo " 3. Configure firewall rules to restrict access" + echo " 4. Configure reverse proxy and HTTPS for production" + echo " 5. Backup database regularly: docker exec ppanel-mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD $MYSQL_DB > backup.sql" + echo "" +} + +# Show firewall configuration info +show_firewall_info() { + if command -v ufw &> /dev/null; then + log_info "Ubuntu/Debian firewall configuration:" + echo " sudo ufw allow $HOST_PORT/tcp" + echo " sudo ufw status" + echo "" + elif command -v firewall-cmd &> /dev/null; then + log_info "CentOS/RHEL firewall configuration:" + echo " sudo firewall-cmd --permanent --add-port=$HOST_PORT/tcp" + echo " sudo firewall-cmd --reload" + echo "" + fi +} + +# Main function +main() { + echo "========================================" + echo " PPanel One-Click Installer" + echo "========================================" + echo "" + + check_docker + check_port + create_directories + get_user_input + create_config + create_docker_compose + pull_image + start_service + wait_for_service + show_access_info + show_firewall_info +} + +main "$@" diff --git a/docs/public/scripts/zh/install-docker.sh b/docs/public/scripts/zh/install-docker.sh new file mode 100755 index 0000000..4a29b45 --- /dev/null +++ b/docs/public/scripts/zh/install-docker.sh @@ -0,0 +1,216 @@ +#!/bin/bash + +####################################################### +# Docker & Docker Compose 安装脚本 +# 支持: Ubuntu/Debian, CentOS/RHEL +# 用法: curl -fsSL https://ppanel.dev/scripts/zh/install-docker.sh | bash +####################################################### + +set -e + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 日志函数 +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查是否为 root 用户 +check_root() { + if [[ $EUID -ne 0 ]]; then + log_error "此脚本必须以 root 权限运行" + log_info "请使用: sudo bash $0" + exit 1 + fi +} + +# 检测系统类型 +detect_os() { + if [[ -f /etc/os-release ]]; then + . /etc/os-release + OS=$ID + VER=$VERSION_ID + else + log_error "无法检测操作系统类型" + exit 1 + fi + + log_info "检测到系统: $OS $VER" +} + +# 检查 Docker 是否已安装 +check_docker_installed() { + if command -v docker &> /dev/null; then + DOCKER_VERSION=$(docker --version | cut -d ' ' -f3 | cut -d ',' -f1) + log_warn "Docker 已安装 (版本: $DOCKER_VERSION)" + read -p "是否重新安装? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "跳过 Docker 安装" + return 0 + fi + fi + return 1 +} + +# 安装 Docker - Ubuntu/Debian +install_docker_debian() { + log_info "开始在 Ubuntu/Debian 上安装 Docker..." + + # 更新包索引 + apt-get update + + # 安装依赖 + apt-get install -y \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + + # 添加 Docker 官方 GPG 密钥 + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/$OS/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + chmod a+r /etc/apt/keyrings/docker.gpg + + # 设置仓库 + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$OS \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + + # 安装 Docker Engine + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + log_info "Docker 安装完成" +} + +# 安装 Docker - CentOS/RHEL +install_docker_centos() { + log_info "开始在 CentOS/RHEL 上安装 Docker..." + + # 安装 yum-utils + yum install -y yum-utils + + # 添加 Docker 仓库 + yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + + # 安装 Docker Engine + yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + log_info "Docker 安装完成" +} + +# 启动 Docker 服务 +start_docker() { + log_info "启动 Docker 服务..." + + systemctl start docker + systemctl enable docker + + log_info "Docker 服务已启动并设置为开机自启" +} + +# 配置 Docker 用户组(可选) +configure_docker_group() { + log_info "配置 Docker 用户组..." + + if ! getent group docker > /dev/null; then + groupadd docker + fi + + if [[ -n "$SUDO_USER" ]]; then + usermod -aG docker "$SUDO_USER" + log_info "用户 $SUDO_USER 已添加到 docker 组" + log_warn "请注销并重新登录以使组权限生效" + fi +} + +# 验证安装 +verify_installation() { + log_info "验证 Docker 安装..." + + # 检查 Docker 版本 + if docker --version &> /dev/null; then + DOCKER_VERSION=$(docker --version) + log_info "✓ $DOCKER_VERSION" + else + log_error "Docker 安装失败" + exit 1 + fi + + # 检查 Docker Compose 版本 + if docker compose version &> /dev/null; then + COMPOSE_VERSION=$(docker compose version) + log_info "✓ $COMPOSE_VERSION" + else + log_error "Docker Compose 安装失败" + exit 1 + fi + + # 运行测试容器 + log_info "运行测试容器..." + if docker run --rm hello-world &> /dev/null; then + log_info "✓ Docker 运行正常" + else + log_error "Docker 运行测试失败" + exit 1 + fi +} + +# 主函数 +main() { + echo "========================================" + echo " Docker & Docker Compose 安装脚本" + echo "========================================" + echo "" + + check_root + detect_os + + if check_docker_installed; then + verify_installation + exit 0 + fi + + case $OS in + ubuntu|debian) + install_docker_debian + ;; + centos|rhel|rocky|almalinux) + install_docker_centos + ;; + *) + log_error "不支持的操作系统: $OS" + log_info "支持的系统: Ubuntu, Debian, CentOS, RHEL, Rocky Linux, AlmaLinux" + exit 1 + ;; + esac + + start_docker + configure_docker_group + verify_installation + + echo "" + log_info "========================================" + log_info "Docker 和 Docker Compose 安装完成!" + log_info "========================================" + log_info "Docker 版本: $(docker --version)" + log_info "Docker Compose 版本: $(docker compose version)" + echo "" + log_info "下一步: 安装 PPanel" + log_info "运行: curl -fsSL https://ppanel.dev/scripts/install-ppanel.sh | bash" +} + +main "$@" diff --git a/docs/public/scripts/zh/install-ppanel.sh b/docs/public/scripts/zh/install-ppanel.sh new file mode 100755 index 0000000..fec0313 --- /dev/null +++ b/docs/public/scripts/zh/install-ppanel.sh @@ -0,0 +1,417 @@ +#!/bin/bash + +####################################################### +# PPanel 一键安装脚本 +# 支持: Docker Compose 部署 +# 用法: curl -fsSL https://ppanel.dev/scripts/zh/install-ppanel.sh | bash +####################################################### + +set -e + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 配置变量 +INSTALL_DIR="${INSTALL_DIR:-$(pwd)/ppanel}" +DOCKER_IMAGE="ppanel/ppanel:latest" +CONTAINER_NAME="ppanel-service" +HOST_PORT="${HOST_PORT:-8080}" + +# 日志函数 +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_step() { + echo -e "${BLUE}[STEP]${NC} $1" +} + +# 检查 Docker 是否已安装 +check_docker() { + if ! command -v docker &> /dev/null; then + log_error "Docker 未安装" + log_info "请先安装 Docker:" + log_info "curl -fsSL https://ppanel.dev/scripts/install-docker.sh | sudo bash" + exit 1 + fi + + if ! docker compose version &> /dev/null; then + log_error "Docker Compose 未安装" + log_info "请先安装 Docker Compose" + exit 1 + fi + + log_info "✓ Docker 已安装 ($(docker --version))" + log_info "✓ Docker Compose 已安装 ($(docker compose version --short))" +} + +# 检查端口是否被占用 +check_port() { + if command -v netstat &> /dev/null; then + if netstat -tuln | grep -q ":$HOST_PORT "; then + log_error "端口 $HOST_PORT 已被占用" + log_info "请设置其他端口: export HOST_PORT=8081" + exit 1 + fi + elif command -v ss &> /dev/null; then + if ss -tuln | grep -q ":$HOST_PORT "; then + log_error "端口 $HOST_PORT 已被占用" + log_info "请设置其他端口: export HOST_PORT=8081" + exit 1 + fi + fi + log_info "✓ 端口 $HOST_PORT 可用" +} + +# 创建安装目录 +create_directories() { + log_step "创建安装目录..." + + mkdir -p "$INSTALL_DIR"/config + # 设置权限确保容器可以读取配置文件 + chmod -R 755 "$INSTALL_DIR" + + log_info "✓ 目录已创建: $INSTALL_DIR" +} + +# 生成随机密钥 +generate_secret() { + if command -v openssl &> /dev/null; then + openssl rand -hex 32 + else + cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 64 | head -n 1 + fi +} + +# 获取用户输入 +get_user_input() { + log_step "配置 PPanel..." + echo "" + + # MySQL 配置 + MYSQL_USER="ppanel" + MYSQL_PASSWORD=$(generate_secret | cut -c1-16) + MYSQL_DB="ppanel" + MYSQL_ROOT_PASSWORD=$(generate_secret | cut -c1-16) + + log_info "✓ 已生成 MySQL 密码" + + # Redis 配置 + REDIS_PASS=$(generate_secret | cut -c1-16) + + log_info "✓ 已生成 Redis 密码" + + # 生成 JWT 密钥 + JWT_SECRET=$(generate_secret) + log_info "✓ 已生成 JWT 密钥" +} + +# 创建配置文件 +create_config() { + log_step "创建配置文件..." + + cat > "$INSTALL_DIR/config/ppanel.yaml" < "$INSTALL_DIR/docker-compose.yml" <<'COMPOSE_EOF' +services: + mysql: + image: mysql:8.0 + container_name: ppanel-mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DB} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + TZ: Asia/Shanghai + volumes: + - mysql:/var/lib/mysql + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + healthcheck: + test: [ + "CMD-SHELL", + "mysqladmin ping -h localhost -u $$MYSQL_USER -p$$MYSQL_PASSWORD || exit 1" + ] + interval: 5s + timeout: 3s + retries: 10 + start_period: 30s + + redis: + image: redis:7-alpine + container_name: ppanel-redis + restart: always + command: redis-server --requirepass ${REDIS_PASS} --appendonly yes + volumes: + - redis:/data + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 10s + timeout: 3s + retries: 5 + + ppanel-service: + image: ${DOCKER_IMAGE} + container_name: ${CONTAINER_NAME} + restart: always + ports: + - "${HOST_PORT}:8080" + volumes: + - ./config:/app/etc:ro + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + +volumes: + mysql: + redis: +COMPOSE_EOF + + log_info "✓ Docker Compose 配置已创建" + + # 创建 .env 文件 + cat > "$INSTALL_DIR/.env" < /dev/null; then + if curl -s "http://localhost:$HOST_PORT" &> /dev/null; then + log_info "✓ 服务已就绪" + return 0 + fi + elif command -v wget &> /dev/null; then + if wget --quiet --tries=1 --spider "http://localhost:$HOST_PORT" &> /dev/null; then + log_info "✓ 服务已就绪" + return 0 + fi + else + # 如果 curl 和 wget 都不可用,直接检查容器状态 + if docker inspect ppanel-service --format='{{.State.Health.Status}}' 2>/dev/null | grep -q "healthy"; then + log_info "✓ 服务已就绪" + return 0 + fi + fi + + attempt=$((attempt + 1)) + echo -n "." + sleep 2 + done + + echo "" + log_warn "服务启动超时,请检查日志" + log_info "查看日志: cd $INSTALL_DIR && docker compose logs -f" +} + +# 显示访问信息 +show_access_info() { + local internal_ip + local public_ip + + # 获取内网IP + internal_ip=$(hostname -I | awk '{print $1}') + + # 获取外网IP + public_ip=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || curl -s --max-time 5 https://ifconfig.me 2>/dev/null || echo "无法获取") + + echo "" + echo "========================================" + log_info "PPanel 安装完成!" + echo "========================================" + echo "" + log_info "访问地址:" + echo " 内网访问 (局域网):" + echo " 用户面板: http://$internal_ip:$HOST_PORT" + echo " 管理后台: http://$internal_ip:$HOST_PORT/admin/" + echo "" + if [[ "$public_ip" != "无法获取" ]]; then + echo " 外网访问 (公网):" + echo " 用户面板: http://$public_ip:$HOST_PORT" + echo " 管理后台: http://$public_ip:$HOST_PORT/admin/" + echo "" + log_warn "注意: 外网访问需要配置防火墙和路由器端口转发" + else + log_warn "未能获取公网IP,如需外网访问请手动配置" + fi + echo "" + log_info "数据库信息:" + echo " MySQL (容器间通信):" + echo " 地址: mysql:3306" + echo " 用户: $MYSQL_USER" + echo " 密码: $MYSQL_PASSWORD" + echo " 数据库: $MYSQL_DB" + echo " Root 密码: $MYSQL_ROOT_PASSWORD" + echo "" + echo " Redis (容器间通信):" + echo " 地址: redis:6379" + echo " 密码: $REDIS_PASS" + echo "" + log_warn "注意: MySQL 和 Redis 仅在容器网络内可访问,未暴露到宿主机" + echo " 如需从宿主机访问,请编辑 docker-compose.yml 添加端口映射" + echo "" + log_info "安装目录: $INSTALL_DIR" + echo "" + log_info "常用命令:" + echo " 查看状态: cd $INSTALL_DIR && docker compose ps" + echo " 查看日志: cd $INSTALL_DIR && docker compose logs -f" + echo " 重启服务: cd $INSTALL_DIR && docker compose restart" + echo " 停止服务: cd $INSTALL_DIR && docker compose stop" + echo " 启动服务: cd $INSTALL_DIR && docker compose start" + echo "" + log_warn "重要提示:" + echo " 1. 请妥善保管数据库密码信息" + echo " 2. 请及时登录管理后台修改默认密码" + echo " 3. 建议配置防火墙规则限制访问" + echo " 4. 生产环境请配置反向代理和 HTTPS" + echo " 5. 定期备份数据库: docker exec ppanel-mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD $MYSQL_DB > backup.sql" + echo "" +} + +# 配置防火墙提示 +show_firewall_info() { + if command -v ufw &> /dev/null; then + log_info "Ubuntu/Debian 防火墙配置:" + echo " sudo ufw allow $HOST_PORT/tcp" + echo " sudo ufw status" + echo "" + elif command -v firewall-cmd &> /dev/null; then + log_info "CentOS/RHEL 防火墙配置:" + echo " sudo firewall-cmd --permanent --add-port=$HOST_PORT/tcp" + echo " sudo firewall-cmd --reload" + echo "" + fi +} + +# 主函数 +main() { + echo "========================================" + echo " PPanel 一键安装脚本" + echo "========================================" + echo "" + + check_docker + check_port + create_directories + get_user_input + create_config + create_docker_compose + pull_image + start_service + wait_for_service + show_access_info + show_firewall_info +} + +main "$@" diff --git a/docs/zh/guide/installation/binary.md b/docs/zh/guide/installation/binary.md index 512cd7e..a7fc7dd 100644 --- a/docs/zh/guide/installation/binary.md +++ b/docs/zh/guide/installation/binary.md @@ -23,19 +23,29 @@ uname -m 访问 [GitHub Releases](https://github.com/perfect-panel/ppanel/releases) 页面或直接下载: +::: tip 安装目录 +你可以将 PPanel 安装在任意目录,本文档使用 `/opt/ppanel` 作为示例。如果选择其他目录,请相应调整后续命令中的路径。 +::: + ```bash -# 创建安装目录 +# 创建安装目录(可以自定义路径) sudo mkdir -p /opt/ppanel cd /opt/ppanel # 下载 Linux amd64 版本 -wget https://github.com/perfect-panel/ppanel/releases/latest/download/ppanel-linux-amd64.tar.gz +wget https://github.com/perfect-panel/ppanel/releases/latest/download/gateway-linux-amd64.tar.gz # 或下载 Linux arm64 版本 -# wget https://github.com/perfect-panel/ppanel/releases/latest/download/ppanel-linux-arm64.tar.gz +# wget https://github.com/perfect-panel/ppanel/releases/latest/download/gateway-linux-arm64.tar.gz + +# 或下载 macOS amd64 版本 +# wget https://github.com/perfect-panel/ppanel/releases/latest/download/gateway-darwin-amd64.tar.gz + +# 或下载 macOS arm64 版本 (Apple Silicon) +# wget https://github.com/perfect-panel/ppanel/releases/latest/download/gateway-darwin-arm64.tar.gz # 解压 -tar -xzf ppanel-linux-amd64.tar.gz +tar -xzf gateway-linux-amd64.tar.gz # 验证解压的文件 ls -la @@ -44,8 +54,7 @@ ls -la 预期的文件结构: ``` /opt/ppanel/ -├── ppanel-server # 主服务器二进制文件 -├── gateway # 网关二进制文件 +├── gateway # 网关可执行文件 └── etc/ # 配置目录 └── ppanel.yaml # 配置文件 ``` @@ -55,52 +64,92 @@ ls -la ### 步骤 1: 准备配置 ```bash -# 复制示例配置 -sudo cp etc/ppanel.yaml etc/ppanel.yaml.backup - # 编辑配置 -sudo nano etc/ppanel.yaml +sudo nano /opt/ppanel/etc/ppanel.yaml ``` -**基础配置示例:** +**配置示例:** + +::: tip 相对路径 +配置中的路径(如 `Path`、`logs` 等)支持相对路径。相对路径是相对于程序工作目录(WorkingDirectory)的,在 systemd 服务中即 `/opt/ppanel`。 +::: ```yaml -server: - host: 0.0.0.0 - port: 8080 - mode: release # debug, release, 或 test +Host: 0.0.0.0 +Port: 8080 +TLS: + Enable: false + CertFile: "" + KeyFile: "" +Debug: false -database: - type: sqlite - path: /opt/ppanel/data/ppanel.db - # MySQL/PostgreSQL 配置: - # type: mysql - # host: localhost - # port: 3306 - # user: ppanel - # password: your_password - # database: ppanel +Static: + Admin: + Enabled: true + Prefix: /admin + Path: ./static/admin + User: + Enabled: true + Prefix: / + Path: ./static/user -log: - level: info # debug, info, warn, error - path: /opt/ppanel/logs +JwtAuth: + AccessSecret: your-secret-key-change-this + AccessExpire: 604800 -gateway: - port: 8080 - timeout: 30s +Logger: + ServiceName: ApiService + Mode: console + Encoding: plain + TimeFormat: "2006-01-02 15:04:05.000" + Path: logs + Level: info + MaxContentLength: 0 + Compress: false + Stat: true + KeepDays: 0 + StackCooldownMillis: 100 + MaxBackups: 0 + MaxSize: 0 + Rotation: daily + FileTimeFormat: 2006-01-02T15:04:05.000Z07:00 + +MySQL: + Addr: localhost:3306 + Username: your-username + Password: your-password + Dbname: ppanel + Config: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai + MaxIdleConns: 10 + MaxOpenConns: 10 + SlowThreshold: 1000 + +Redis: + Host: localhost:6379 + Pass: your-redis-password + DB: 0 ``` +::: warning 必需配置 +**MySQL 和 Redis 是必需的。** 部署前请配置以下项: +- `JwtAuth.AccessSecret` - 使用强随机密钥(必需) +- `MySQL.*` - 配置你的 MySQL 数据库连接(必需) +- `Redis.*` - 配置你的 Redis 连接(必需) +::: + ### 步骤 2: 创建必要的目录 ```bash # 创建数据和日志目录 sudo mkdir -p /opt/ppanel/data sudo mkdir -p /opt/ppanel/logs +sudo mkdir -p /opt/ppanel/static # 设置适当的权限 sudo chmod 755 /opt/ppanel sudo chmod 700 /opt/ppanel/data sudo chmod 755 /opt/ppanel/logs +sudo chmod 755 /opt/ppanel/static ``` ## 运行服务 @@ -111,15 +160,11 @@ sudo chmod 755 /opt/ppanel/logs ```bash # 使二进制文件可执行 -sudo chmod +x /opt/ppanel/ppanel-server sudo chmod +x /opt/ppanel/gateway -# 直接运行服务器 +# 直接运行 cd /opt/ppanel -sudo ./ppanel-server - -# 在另一个终端运行网关(如果分离) -# sudo ./gateway +sudo ./gateway ``` 按 `Ctrl+C` 停止。 @@ -147,7 +192,7 @@ Wants=network-online.target Type=simple User=root WorkingDirectory=/opt/ppanel -ExecStart=/opt/ppanel/ppanel-server +ExecStart=/opt/ppanel/gateway Restart=always RestartSec=10 @@ -262,7 +307,7 @@ ps aux | grep ppanel ### 访问应用 - **用户面板**: `http://your-server-ip:8080` -- **管理后台**: `http://your-server-ip:8080/admin` +- **管理后台**: `http://your-server-ip:8080/admin/` ### 配置防火墙 @@ -312,70 +357,11 @@ sudo systemctl reload nginx ## 升级 -### 升级前备份 +直接从**管理后台**主页升级 PPanel。在仪表盘主页可以检查新版本并一键升级。 -```bash -# 停止服务 -sudo systemctl stop ppanel - -# 备份当前版本 -sudo cp -r /opt/ppanel /opt/ppanel-backup-$(date +%Y%m%d) - -# 备份数据库 -sudo cp /opt/ppanel/data/ppanel.db /opt/ppanel/data/ppanel.db.backup-$(date +%Y%m%d) - -# 备份配置 -sudo cp /opt/ppanel/etc/ppanel.yaml /opt/ppanel/etc/ppanel.yaml.backup-$(date +%Y%m%d) -``` - -### 下载并安装新版本 - -```bash -# 下载新版本 -cd /tmp -wget https://github.com/perfect-panel/ppanel/releases/latest/download/ppanel-linux-amd64.tar.gz - -# 解压到临时位置 -mkdir ppanel-new -tar -xzf ppanel-linux-amd64.tar.gz -C ppanel-new - -# 备份旧的二进制文件 -sudo mv /opt/ppanel/ppanel-server /opt/ppanel/ppanel-server.old -sudo mv /opt/ppanel/gateway /opt/ppanel/gateway.old - -# 安装新的二进制文件 -sudo cp ppanel-new/ppanel-server /opt/ppanel/ -sudo cp ppanel-new/gateway /opt/ppanel/ - -# 设置权限 -sudo chmod +x /opt/ppanel/ppanel-server -sudo chmod +x /opt/ppanel/gateway - -# 启动服务 -sudo systemctl start ppanel - -# 检查状态 -sudo systemctl status ppanel -``` - -### 回滚 - -如果升级失败: - -```bash -# 停止服务 -sudo systemctl stop ppanel - -# 恢复旧的二进制文件 -sudo mv /opt/ppanel/ppanel-server.old /opt/ppanel/ppanel-server -sudo mv /opt/ppanel/gateway.old /opt/ppanel/gateway - -# 恢复数据库(如需要) -sudo cp /opt/ppanel/data/ppanel.db.backup-YYYYMMDD /opt/ppanel/data/ppanel.db - -# 启动服务 -sudo systemctl start ppanel -``` +::: tip 提示 +系统会自动处理升级过程,包括下载新的二进制文件和重启服务。 +::: ## 故障排除 @@ -413,14 +399,14 @@ sudo systemctl restart ppanel ```bash # 检查架构兼容性 uname -m -file /opt/ppanel/ppanel-server +file /opt/ppanel/gateway # 检查是否可执行 -ls -la /opt/ppanel/ppanel-server -sudo chmod +x /opt/ppanel/ppanel-server +ls -la /opt/ppanel/gateway +sudo chmod +x /opt/ppanel/gateway # 检查缺失的库(静态编译应该没有) -ldd /opt/ppanel/ppanel-server +ldd /opt/ppanel/gateway ``` ### 内存使用过高 @@ -497,7 +483,7 @@ sudo nano /etc/systemd/system/ppanel.service # 更改: User=ppanel # 如果绑定到端口 < 1024,授予能力 -sudo setcap 'cap_net_bind_service=+ep' /opt/ppanel/ppanel-server +sudo setcap 'cap_net_bind_service=+ep' /opt/ppanel/gateway sudo systemctl daemon-reload sudo systemctl restart ppanel diff --git a/docs/zh/guide/installation/docker-compose.md b/docs/zh/guide/installation/docker-compose.md index 2b5a1f0..9df8d42 100644 --- a/docs/zh/guide/installation/docker-compose.md +++ b/docs/zh/guide/installation/docker-compose.md @@ -77,17 +77,17 @@ cd ~/ppanel version: '3.8' services: - ppanel: + ppanel-service: image: ppanel/ppanel:latest - container_name: ppanel + container_name: ppanel-service + restart: always ports: - "8080:8080" volumes: - - ./ppanel-config:/app/etc:ro - - ppanel-data:/app/data - restart: unless-stopped - environment: - - TZ=Asia/Shanghai + - ./config:/app/etc:ro + - ./web:/app/static + networks: + - ppanel-net healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"] interval: 30s @@ -95,45 +95,92 @@ services: retries: 3 start_period: 40s -volumes: - ppanel-data: - driver: local +networks: + ppanel-net: + driver: bridge ``` **配置说明:** - **image**: 使用的 Docker 镜像(latest 或指定版本如 `v0.1.2`) -- **ports**: 将容器的 8080 端口映射到宿主机的 8080 端口 +- **container_name**: 设置自定义容器名称 +- **ports**: 将容器的 8080 端口映射到宿主机的 8080 端口(可根据需要修改宿主机端口) - **volumes**: - - `./ppanel-config:/app/etc:ro` - 配置目录(只读) - - `ppanel-data:/app/data` - 持久化数据存储 -- **restart**: 自动重启策略 -- **environment**: 设置时区(可改为 `Asia/Shanghai` 等) + - `./config:/app/etc:ro` - 配置目录(只读) + - `./web:/app/static` - 静态文件目录(管理后台和用户前端) +- **networks**: 创建自定义网络以实现服务隔离 +- **restart**: 自动重启策略(always 表示总是重启) - **healthcheck**: 服务健康检查 ### 步骤 3: 准备配置 ```bash # 创建配置目录 -mkdir -p ppanel-config +mkdir -p config # 创建配置文件 -cat > ppanel-config/ppanel.yaml < config/ppanel.yaml < 容器端口 8080 +services: + ppanel-service: + ports: + - "3000:8080" # 宿主机端口 3000 -> 容器端口 8080 ``` ### 多实例部署 @@ -403,10 +420,10 @@ sudo lsof -i :8080 ```bash # 修复配置目录权限 -sudo chown -R $USER:$USER ppanel-config/ +sudo chown -R $USER:$USER config/ # 确保文件可读 -chmod 644 ppanel-config/ppanel.yaml +chmod 644 config/ppanel.yaml ``` ### 无法从外部访问 diff --git a/docs/zh/guide/installation/docker-run.md b/docs/zh/guide/installation/docker-run.md index 1adfa94..b7eeca1 100644 --- a/docs/zh/guide/installation/docker-run.md +++ b/docs/zh/guide/installation/docker-run.md @@ -67,53 +67,106 @@ mkdir -p ~/ppanel-config # 创建配置文件 cat > ~/ppanel-config/ppanel.yaml <