commit 67d82529a6aa9bb28aecd38c7894a955b7acbf82 Author: Lemon-miaow Date: Fri Oct 2 19:58:16 2026 +0800 Set up bilingual Felis documentation site diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73f30a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +docs/.vitepress/cache/ +docs/.vitepress/dist/ +.DS_Store +.env*.local diff --git a/README.md b/README.md new file mode 100644 index 0000000..50af455 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# Felis 文档站 + +基于 VitePress 默认主题,使用 Felis 黄绿色配色。参考 [Vdrias World Manual](https://github.com/vdriasworld/manual) 的插件选型,接入 Nólëbase 阅读增强与标题定位;使用 Mermaid 渲染主仓库的时序图。导航、搜索、代码复制和主题切换由 VitePress 提供。 + +## 本地开发 + +需要 Node.js 22+ 与 Bun 1.3.14。 + +```sh +bun install --frozen-lockfile +bun run docs:dev +``` + +```sh +bun run docs:build +bun run docs:preview +``` + +文档放在 `docs/`,导航与侧栏在 `docs/.vitepress/config.mts`,品牌色与样式在 `docs/.vitepress/theme/style.css`。入口直接显示带侧栏的文档正文,沿用 VitePress 默认主题。 + +## 中英文内容 + +仅提供简体中文与英文。中文页面沿用 `docs/` 下的现有路径,英文对应页面放在 `docs/en/`,例如 `docs/guide/deployment.md` 对应 `docs/en/guide/deployment.md`。VitePress 的语言菜单切换到同一篇文章,导航、侧栏、搜索与阅读增强菜单随语言切换。搜索索引按语言划分。 + +修改技术内容时同时更新两种语言,命令、配置键、错误码和验证标记保持一致。标题末尾的 `{#章节标识}` 在对应页面中使用同一值;翻译标题时保留该标识,避免旧链接和语言切换后的章节定位失效。OpenAPI、许可证和图片由两种语言共用 `docs/public/` 下的原件。 + +## 发布 + +使用 Bun 安装依赖和执行 VitePress 构建,产物目录为 `docs/.vitepress/dist`。 + +### Cloudflare Pages + +连接本仓库后,填写以下构建设置: + +| 设置 | 值 | +| --- | --- | +| 框架预设 | `VitePress`,随后覆盖下面的命令和输出目录 | +| 根目录 | 留空(仓库根目录) | +| 构建命令 | `bun install --frozen-lockfile && bun run docs:build` | +| 构建输出目录 | `docs/.vitepress/dist` | + +生产环境和预览环境都添加以下环境变量: + +| 变量 | 值 | +| --- | --- | +| `BUN_VERSION` | `1.3.14` | +| `NODE_VERSION` | `22` | +| `SKIP_DEPENDENCY_INSTALL` | `1` | + +构建命令按 `bun.lock` 显式安装依赖,因此关闭 Pages 的自动依赖安装。`pages.dev` 或独立文档域名使用默认的 `/`,无需设置 `DOCS_BASE`。版本变量与跳过自动安装的设置参见 [Cloudflare 构建环境文档](https://developers.cloudflare.com/pages/configuration/build-image/)。 + +### 部署到子路径 + +如果使用 `https://felismc.github.io/docs/`,构建时设置 `DOCS_BASE=/docs/`: + +```sh +DOCS_BASE=/docs/ bun run docs:build +``` + +## 内容来源 + +内容以 [Felis 主仓库](https://github.com/FelisMC/Felis) 为准,本次迁移基于本地提交 `2e4f118939aec47852c181f8043f8ab98c62014c`。两种语言保留原文的命令、限制与验证标记。入门内容分别使用 `README.md`、`README_EN.md`;技术文章的译文与对应原文保持相同结构和章节标识。 + +| 主仓库来源 | 文档站位置 | +| --- | --- | +| `README.md` | 认识 Felis、安装与部署、管理服务器、备份与恢复、从源码构建、开源协议 | +| `docs/operations.md` | `docs/operations/index.md` | +| `docs/troubleshooting.md` | `docs/operations/troubleshooting.md` | +| `docs/distributed.md` | `docs/guide/distributed.md` | +| `docs/sequence-diagrams.md` | `docs/reference/sequence-diagrams.md` | +| `docs/deferred-seams.md` | `docs/reference/deferred-seams.md` | +| `CONTRIBUTING.md` | `docs/reference/contributing.md` | +| `plugins/README.md` | `docs/reference/plugins.md` | +| `deploy/lobby/README.md`、`deploy/limbo/README.md` | `docs/reference/lobby.md`、`docs/reference/limbo.md` | +| `README.md`、`README_EN.md` | 中英文的 `reference/readme-en.md`(项目说明,保留原有地址) | +| `docs/openapi.yaml` | `docs/public/openapi.yaml`,由 API 定义页面提供下载 | +| `LICENSE` | `docs/public/LICENSE.txt` | + +部署架构及入门页面摘取上述原文,并链接到完整手册。更新功能说明时先核对主仓库对应文章,保留其发布通道、实验性状态与验证边界,避免独立改写出另一套口径。每页末尾均链接到主仓库原文。 + +渲染适配:故障排查里跨行的行内代码已合并一处换行;时序图的文本分号使用 [Mermaid 要求的 `#59;` 转义](https://mermaid.js.org/syntax/sequenceDiagram.html#entity-codes-to-escape-characters);数字开头的章节锚点按 VitePress 的格式添加 `_`,两种语言通过显式章节标识共享锚点。README 的居中品牌页眉改为普通 Markdown。 diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..522ee9a --- /dev/null +++ b/bun.lock @@ -0,0 +1,650 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "felis-docs", + "devDependencies": { + "@nolebase/vitepress-plugin-enhanced-readabilities": "2.18.2", + "@nolebase/vitepress-plugin-highlight-targeted-heading": "2.18.2", + "mermaid": "11.17.2", + "vitepress": "1.6.4", + "vitepress-plugin-mermaid": "2.0.17", + "vue": "^3.5.43", + }, + }, + }, + "packages": { + "@algolia/abtesting": ["@algolia/abtesting@1.25.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-rSTin9Uta23uaewYVQEp8XI9T3iA/zrg0/1G2vhf8oFFDxFL5vybnZ5IQwsVAg4JpKxPX4/WYNKdcfWrZymk7w=="], + + "@algolia/autocomplete-core": ["@algolia/autocomplete-core@1.17.7", "", { "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", "@algolia/autocomplete-shared": "1.17.7" } }, "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q=="], + + "@algolia/autocomplete-plugin-algolia-insights": ["@algolia/autocomplete-plugin-algolia-insights@1.17.7", "", { "dependencies": { "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A=="], + + "@algolia/autocomplete-preset-algolia": ["@algolia/autocomplete-preset-algolia@1.17.7", "", { "dependencies": { "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA=="], + + "@algolia/autocomplete-shared": ["@algolia/autocomplete-shared@1.17.7", "", { "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg=="], + + "@algolia/client-abtesting": ["@algolia/client-abtesting@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-bm2XN0hCSMYwStSsCBT0/PUB2BDxoyR1Lnub3c392HMEy9bi8PUSW8vR6zltVEKWG2t4PQFWMD5C07bmJxGgPg=="], + + "@algolia/client-analytics": ["@algolia/client-analytics@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-XOFPOTa69WuqHR6c5tMgnUUwwqQgNSzMpxmhrgA9KmxRf8WIqEa0cokHJvohk5CYb7CZx0xeSL6Bk2IUJ7Lv7Q=="], + + "@algolia/client-common": ["@algolia/client-common@5.59.0", "", {}, "sha512-PC8ipLOYFKRTfIUY1J3FJxS6ryzWziaXmIX9/sNMoUR8L+XhF7hX2QAeUI87YVl5zujvlYTSOb+HAIqKXDjyHQ=="], + + "@algolia/client-insights": ["@algolia/client-insights@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-yFNcCMM5fHiyoR0HuxMrzy+VjDcmhFUZTm2IJ2DHwGsVH3B5SEob4zTmeEZ3j/AZqNnmrJOCKl/tJnBg7+84bA=="], + + "@algolia/client-personalization": ["@algolia/client-personalization@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-GYja6HkDt2VrQhWmB2cLx3Z5fDwI9no7q+xwCWcFrTPm5CLH9QZvK0XLO9co1FcNIDxxkMaP3AXM39/XcecEOg=="], + + "@algolia/client-query-suggestions": ["@algolia/client-query-suggestions@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-Wofg7bMpWh8N5qDDZs0wy6whc+KMmdNsxgrIGp9Ug2s1Bka0uq7AjyckdxReKPHLA0Q1qH8X/SNh0wn2t2X2Lw=="], + + "@algolia/client-search": ["@algolia/client-search@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-fHnALZfbEnODczGk14Y/1YBRApp6UEpZUTexGcMUPzY7RDc7q4HN2Y6jh0KUG8Jo9B+q+wOoQEc3ziR0ErbuFg=="], + + "@algolia/ingestion": ["@algolia/ingestion@1.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-Fa38s1mHgoaLCT117sfJ6P78rtxUt93CYxBYpq1VOIcslaF+cH+1h5uikAPsKfxLnsuEDZHiBiaI6eNPTsQMRA=="], + + "@algolia/monitoring": ["@algolia/monitoring@1.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-NyNsRSqM2tF1MX7ZGw/j4rduoEJiQ5wfvbSo/CFVdEzYCjyxbGFRPOZyS/GETJG9rk1TdHOq8NZqKqk/u1fm4g=="], + + "@algolia/recommend": ["@algolia/recommend@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-nXBK2uygWtvbCOffMWqqfjjcEtp9enDKY5/2Pw/Hhw8VVaOyK2ThbGK04bNX/Le+qoK1VHYkodWGVTKfw0Otkw=="], + + "@algolia/requester-browser-xhr": ["@algolia/requester-browser-xhr@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0" } }, "sha512-yb+4afX/zja8QwX0KmV4/ae2kyYkRknsE79CRusYS2U5D8qwn2wq0cn1x12f3tm3F3+5FgmDdbuGdQTuCLSKMQ=="], + + "@algolia/requester-fetch": ["@algolia/requester-fetch@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0" } }, "sha512-Lp52TmpA1QtNmdHzs505Xwf4tgII7RAapkDSF9AAtWIBETcGaTIaXci4Rd9nS6SdaWK1BWTSqAPp5wzh6UN3Ag=="], + + "@algolia/requester-node-http": ["@algolia/requester-node-http@5.59.0", "", { "dependencies": { "@algolia/client-common": "5.59.0" } }, "sha512-YYHLEs5rC6oRTFwT7bJaKBR9NSFzikeVHGAT1ffATmUQzR8XqyhHXjoieAUoR7fBU6I5cfy1FVeh5GoRgriuzA=="], + + "@antfu/install-pkg": ["@antfu/install-pkg@2.1.0", "", { "dependencies": { "package-manager-detector": "^1.8.0", "tinyexec": "^1.3.1" } }, "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/parser": ["@babel/parser@7.29.9", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], + + "@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="], + + "@docsearch/css": ["@docsearch/css@3.8.2", "", {}, "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ=="], + + "@docsearch/js": ["@docsearch/js@3.8.2", "", { "dependencies": { "@docsearch/react": "3.8.2", "preact": "^10.0.0" } }, "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ=="], + + "@docsearch/react": ["@docsearch/react@3.8.2", "", { "dependencies": { "@algolia/autocomplete-core": "1.17.7", "@algolia/autocomplete-preset-algolia": "1.17.7", "@docsearch/css": "3.8.2", "algoliasearch": "^5.14.2" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 19.0.0", "react": ">= 16.8.0 < 19.0.0", "react-dom": ">= 16.8.0 < 19.0.0", "search-insights": ">= 1 < 3" }, "optionalPeers": ["@types/react", "react", "react-dom", "search-insights"] }, "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + + "@iconify-json/carbon": ["@iconify-json/carbon@1.2.27", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-tufpkYXBDlXqczQEZo6QgvfwBd5aIK36gt+pHOI66eK4KOO8oh7CIHDZ4JsiSnOyjqRbApVHzXv4Nz8NO0GPFQ=="], + + "@iconify-json/icon-park-outline": ["@iconify-json/icon-park-outline@1.2.4", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-NyZxXe2gD2TbTOyoRRMdtEJhr6i2KQCdDlYYoOn5oZLndQjwpIhw79hzeFhXvP38/o40D3gQ+l+IaSJgbB+0TQ=="], + + "@iconify-json/octicon": ["@iconify-json/octicon@1.2.36", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-YHlJokjC+uBj1kSd7+YTRMNIN85TvAGr4ZJVOHCTzj0nyH64ImXRIZmh4P6Nbv0RGDd7hS7+UwEDGchFgGxxVw=="], + + "@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.97", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-Z6PmM6jvWVU7rmLzqbxCLJA7SeqCnK6lMHXtjyzHPLZebwuoojBEcHHycppahDzEeN9xujRkrV5J8QpOhMyNrg=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.1.7", "", { "dependencies": { "@antfu/install-pkg": "^2.0.1", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], + + "@mermaid-js/mermaid-mindmap": ["@mermaid-js/mermaid-mindmap@9.3.0", "", { "dependencies": { "@braintree/sanitize-url": "^6.0.0", "cytoscape": "^3.23.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.1.0", "d3": "^7.0.0", "khroma": "^2.0.0", "non-layered-tidy-tree-layout": "^2.0.2" } }, "sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw=="], + + "@mermaid-js/parser": ["@mermaid-js/parser@1.2.1", "", { "dependencies": { "@chevrotain/types": "~11.1.2" } }, "sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw=="], + + "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + + "@nolebase/ui": ["@nolebase/ui@2.18.2", "", { "dependencies": { "@iconify-json/octicon": "^1.2.10", "less": "^4.4.0" }, "peerDependencies": { "vitepress": "^1.5.0 || ^2.0.0-alpha.1", "vue": ">= 3.5.18" } }, "sha512-xxfRacF9cqQ5/umMhvhr0y2W4SkhzTmrrAHJ0UAAu/pIWfV/JPE9Hj0buH06bK7ZEUur+036gxkKlStI6UtDBw=="], + + "@nolebase/vitepress-plugin-enhanced-readabilities": ["@nolebase/vitepress-plugin-enhanced-readabilities@2.18.2", "", { "dependencies": { "@iconify-json/carbon": "^1.2.11", "@iconify-json/icon-park-outline": "^1.2.2", "@nolebase/ui": "^2.18.2", "less": "^4.4.0" }, "peerDependencies": { "vitepress": "^1.5.0 || ^2.0.0-alpha.1" } }, "sha512-fDhdZBSJL2qs/1xac0PtJfU5UI7b36ffVPYgzM8Ig2NjqMJ7cBvrTTPGq03lUeMjK7GD7fItAKudaP+ZrtMg8w=="], + + "@nolebase/vitepress-plugin-highlight-targeted-heading": ["@nolebase/vitepress-plugin-highlight-targeted-heading@2.18.2", "", { "dependencies": { "less": "^4.4.0" }, "peerDependencies": { "vitepress": "^1.5.0 || ^2.0.0-alpha.1" } }, "sha512-RVrT7FgyjxrnFFR9twc1OJI/5B09+biVbSiO36Yiu2YC+kRMz1Mb553l2DyioLtWJgVNTJ7sj66ZJi40JpDh7g=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.63.5", "", { "os": "android", "cpu": "arm" }, "sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.63.5", "", { "os": "android", "cpu": "arm64" }, "sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.63.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wlJEERGfeuHeBavCL2qVnNacOK43NDoZM4sjkeRPymd04OAE9T1zBqDJgmZ+CIsPTYKwdzpUC8vmOw84dwY4Tg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.63.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-4nJJGg5jbo2wwPP4JP+LfEBA3bvP8rU9CLuhp7jWvq9sxEyhjQFTFdrqi+/dHEin/pd8jpT0vcehIpnZtmEdcQ=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.63.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-DrZbyCDF1hneuO6jRbvZ2D7+PIBM6yIwYnJpg2vIk58T+wuFpiaGZrfUr59lDWw45bg+IrpTGLPiNi/Fk4w3Cg=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.63.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-gqfUVMJMB3mehqywxp6hTBFfgtMQykZY19+cfiaYP0toIJLb/1DZRJHVkQQGP13W4TAwfZDWeg1qBcheTRioXQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.63.5", "", { "os": "linux", "cpu": "arm" }, "sha512-CFmhpvAwzSaWMlN3VN7UtmoTihlZNzoP0juQib5TQRnYUyDV8dXeWOp29sobWAT6gXl/hQgAClLlEiYozQG3OQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.63.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Uc9H8eXCOayV6JLTH5bXKMId6qbhNHa818/BgYjm4jrlq3vZquC9cqyvHBw17xy5Mnj5f+I3gFK5JcEf3hSqrw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.63.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-VcPr/szv/1BFw112Kt//fxulXt/JPqzzidU84iW68L2DdjnOO8QFUv2zTSYBEPHD6movBD4z+bbr5y60GYM7Jw=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.63.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-BnxtJ5/91BrIHYIkGrmjz/lbMhqEHt1dPFqIxIFR+jPn0xVc/oUSCtIT089zfp5ufwGDlYz2UC+Fe1SRBpYFbQ=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-LrYcHZwF+fAMNKHYTOQ5osWM4AZF7YF6D+XtsjDyEvljtt11twc+zHVXBLNEjxVSUnKYsOhvVz4Z213eW02COQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-nj7QKQePAAUpCpJHtg0pR0W/b92A9NO17JS3BAQmHDn/yhmkir2p8llrKY9TOhleKIaSzy1JhxS3T9FVld6coA=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.63.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5ylkX6dWMeBKge9nTU+Rxfb+ZfaCIJ9lRqIFaK0eAMcWp7OJbYnLveLgXmm0VrvuLKb8qIK+mHyH0qu88RM+iA=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.63.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-oHK4ZHYFDKjZviK34I+NwgfbGxgI7ztrNxj2hPTSSNFgeq1a/lEd7dHV2fdGAuTH4Iym3RHJg+vAbWaWG4B7Zg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-UcetmHZ6XOXuUByiKZyQmb55ZPr0LABr3Ec/HB9wKZn6CEAFWZkE+hsJErJ9hbPBC7nI0dKuELx7CoV6IM7TMg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.63.5", "", { "os": "linux", "cpu": "none" }, "sha512-C5CmDPQBtvjVo8cgQsBs+w6WB0JLkiixhgi6hVLV11hERWdn/p0XcPU2OUcZzac9BPOFq7SbaHFa8r3SWEysCQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.63.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-lHVQHJFKsuuxLMi3MQO9XVL8Tje3JR82CzB+QDKC5NWBcsIWuwsn9uIM5e3lBhI+fF1/s63qnyYqsg65+8rV/w=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.63.5", "", { "os": "linux", "cpu": "x64" }, "sha512-3W9bTFcQNJn71cSJVM9RKIiZOy8DO/XLDii8Uv/Pm6WKqDRj7JV3ZfuXIEfyuy5LXpIzAbB/1M4Ukp9GKNa7nA=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.63.5", "", { "os": "linux", "cpu": "x64" }, "sha512-VDC7rRJlee/scpki96GZ27Omf6yU87s1YXwVTpjE5841faVlDYYT565rgfmoR1U0sqL7z5ivQSDjcsF6VRXyBA=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.63.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-z86Ok2p4pTdv5xqCKZsTooO7yBEiaJR/HzU3Wx8RmWsPoLppnMKROhJusQob8B3IE1ghC343kUW9rC2r+Wf3ig=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.63.5", "", { "os": "none", "cpu": "arm64" }, "sha512-IzQmj+xXwQFGhMAMKMQVXkMwMZN3TqkJgAE0nSsqvVwWWciP4AIPMmWRqOQ2GfX7TUDZr+xqGFcBS36CRPGw0g=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.63.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-F6qpTaPc9bwBH85kjy0/BLmLSW1uv7AoOXCoRIkg2arlgCYlWYcAbiMkvZuAcaWk9TpCRG//okznLAqLGshkMw=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.63.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-igoDsTFhhwECBeGbUuLeIk7t8Y1apa+cs6mDWpx2EZ0ch7oEQgzHbFUXN9euoHekCAQzXdXApAGkV6jznS7tWw=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.63.5", "", { "os": "win32", "cpu": "x64" }, "sha512-U3teMeMbXFmaM5D+OTJpsOXd+wV/qftIeYF9kBKL4v73641qyJmoXFtA28DQLsnmlyayEsTe72xpLHrArq6vHw=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.63.5", "", { "os": "win32", "cpu": "x64" }, "sha512-ypfC34F3RKXvCXBglGqGMsUSMKlgwd1HX9AOAlx9RoZZ6GaI42YHVeKpzg3JG+wpBUJYTG+NNZhqbDWL8tBZkw=="], + + "@shikijs/core": ["@shikijs/core@2.5.0", "", { "dependencies": { "@shikijs/engine-javascript": "2.5.0", "@shikijs/engine-oniguruma": "2.5.0", "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^3.1.0" } }, "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw=="], + + "@shikijs/langs": ["@shikijs/langs@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0" } }, "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w=="], + + "@shikijs/themes": ["@shikijs/themes@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0" } }, "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw=="], + + "@shikijs/transformers": ["@shikijs/transformers@2.5.0", "", { "dependencies": { "@shikijs/core": "2.5.0", "@shikijs/types": "2.5.0" } }, "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg=="], + + "@shikijs/types": ["@shikijs/types@2.5.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + + "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], + + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="], + + "@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="], + + "@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], + + "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], + + "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="], + + "@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="], + + "@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], + + "@types/d3-geo": ["@types/d3-geo@3.1.1", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w=="], + + "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="], + + "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="], + + "@types/d3-random": ["@types/d3-random@3.0.4", "", {}, "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.12", "", {}, "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw=="], + + "@types/d3-shape": ["@types/d3-shape@3.2.0", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], + + "@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], + + "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], + + "@types/markdown-it": ["@types/markdown-it@14.2.0", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.4.0", "", {}, "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ=="], + + "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="], + + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.4", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.43", "", { "dependencies": { "@vue/compiler-core": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ=="], + + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.43", "@vue/compiler-dom": "3.5.43", "@vue/compiler-ssr": "3.5.43", "@vue/shared": "3.5.43", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.28", "source-map-js": "^1.2.1" } }, "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ=="], + + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], + + "@vue/devtools-api": ["@vue/devtools-api@7.7.10", "", { "dependencies": { "@vue/devtools-kit": "^7.7.10" } }, "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA=="], + + "@vue/devtools-kit": ["@vue/devtools-kit@7.7.10", "", { "dependencies": { "@vue/devtools-shared": "^7.7.10", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw=="], + + "@vue/devtools-shared": ["@vue/devtools-shared@7.7.10", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ=="], + + "@vue/reactivity": ["@vue/reactivity@3.5.43", "", { "dependencies": { "@vue/shared": "3.5.43" } }, "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA=="], + + "@vue/runtime-core": ["@vue/runtime-core@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw=="], + + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/runtime-core": "3.5.43", "@vue/shared": "3.5.43", "csstype": "^3.2.3" } }, "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw=="], + + "@vue/server-renderer": ["@vue/server-renderer@3.5.43", "", { "dependencies": { "@vue/compiler-ssr": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ=="], + + "@vue/shared": ["@vue/shared@3.5.43", "", {}, "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q=="], + + "@vueuse/core": ["@vueuse/core@12.8.2", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "12.8.2", "@vueuse/shared": "12.8.2", "vue": "^3.5.13" } }, "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ=="], + + "@vueuse/integrations": ["@vueuse/integrations@12.8.2", "", { "dependencies": { "@vueuse/core": "12.8.2", "@vueuse/shared": "12.8.2", "vue": "^3.5.13" }, "peerDependencies": { "async-validator": "^4", "axios": "^1", "change-case": "^5", "drauu": "^0.4", "focus-trap": "^7", "fuse.js": "^7", "idb-keyval": "^6", "jwt-decode": "^4", "nprogress": "^0.2", "qrcode": "^1.5", "sortablejs": "^1", "universal-cookie": "^7" }, "optionalPeers": ["async-validator", "axios", "change-case", "drauu", "focus-trap", "fuse.js", "idb-keyval", "jwt-decode", "nprogress", "qrcode", "sortablejs", "universal-cookie"] }, "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g=="], + + "@vueuse/metadata": ["@vueuse/metadata@12.8.2", "", {}, "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A=="], + + "@vueuse/shared": ["@vueuse/shared@12.8.2", "", { "dependencies": { "vue": "^3.5.13" } }, "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w=="], + + "algoliasearch": ["algoliasearch@5.59.0", "", { "dependencies": { "@algolia/abtesting": "1.25.0", "@algolia/client-abtesting": "5.59.0", "@algolia/client-analytics": "5.59.0", "@algolia/client-common": "5.59.0", "@algolia/client-insights": "5.59.0", "@algolia/client-personalization": "5.59.0", "@algolia/client-query-suggestions": "5.59.0", "@algolia/client-search": "5.59.0", "@algolia/ingestion": "1.59.0", "@algolia/monitoring": "1.59.0", "@algolia/recommend": "5.59.0", "@algolia/requester-browser-xhr": "5.59.0", "@algolia/requester-fetch": "5.59.0", "@algolia/requester-node-http": "5.59.0" } }, "sha512-wUXzaeI7B526W4y1gFg3lcxgDZ67XSgRJIiellYWOas/pLpO7rOtxm9Gr2E/C8aiSDXIgx1q5TdSsvK67Uakqw=="], + + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "copy-anything": ["copy-anything@3.0.5", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="], + + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "cytoscape": ["cytoscape@3.34.3", "", {}, "sha512-yfYGhRcGAntq6YBD583j4n0Eg3jIxvWmZtz/5uz9UYkeIStSlMxuUja+ec5j3iBD8nv1rwaOAYMW09tBdkSeaQ=="], + + "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], + + "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="], + + "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], + + "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], + + "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], + + "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], + + "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="], + + "dayjs": ["dayjs@1.11.23", "", {}, "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ=="], + + "debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + + "delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "dompurify": ["dompurify@3.4.16", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ=="], + + "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "errno": ["errno@0.1.8", "", { "dependencies": { "prr": "~1.0.1" }, "bin": { "errno": "cli.js" } }, "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A=="], + + "es-toolkit": ["es-toolkit@1.52.0", "", {}, "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA=="], + + "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "fastdom": ["fastdom@1.0.12", "", { "dependencies": { "strictdom": "^1.0.1" } }, "sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg=="], + + "focus-trap": ["focus-trap@7.8.0", "", { "dependencies": { "tabbable": "^6.4.0" } }, "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + + "is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="], + + "katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="], + + "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + + "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], + + "less": ["less@4.9.1", "", { "dependencies": { "copy-anything": "^3.0.5", "parse-node-version": "^1.0.1" }, "optionalDependencies": { "errno": "^0.1.1", "graceful-fs": "^4.1.2", "make-dir": "^5.1.0", "mime": "^1.4.1", "needle": "^3.1.0", "probe-image-size": "^7.2.3", "source-map": "~0.6.0" }, "bin": { "lessc": "bin/lessc" } }, "sha512-orp15PfJvvNDIqJdVWzMI9Sjpjp3VTiw3sfvbB+67LlISTEn8uVT2EdYSuyl02BLvaftv6sdk9Umnxmm5rckmg=="], + + "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "make-dir": ["make-dir@5.1.0", "", {}, "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw=="], + + "mark.js": ["mark.js@8.11.1", "", {}, "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ=="], + + "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mermaid": ["mermaid@11.17.2", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.2.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.34.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.21", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", "fastdom": "1.0.12", "katex": "^0.16.47", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "minisearch": ["minisearch@7.2.0", "", {}, "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg=="], + + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.19", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug=="], + + "needle": ["needle@3.5.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "sax": "^1.2.4" }, "bin": { "needle": "bin/needle" } }, "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w=="], + + "non-layered-tidy-tree-layout": ["non-layered-tidy-tree-layout@2.0.2", "", {}, "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="], + + "oniguruma-to-es": ["oniguruma-to-es@3.1.1", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ=="], + + "package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], + + "parse-node-version": ["parse-node-version@1.0.1", "", {}, "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA=="], + + "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + + "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], + + "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], + + "postcss": ["postcss@8.5.28", "", { "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A=="], + + "preact": ["preact@10.29.8", "", { "peerDependencies": { "preact-render-to-string": ">=5" }, "optionalPeers": ["preact-render-to-string"] }, "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q=="], + + "probe-image-size": ["probe-image-size@7.4.0", "", { "dependencies": { "lodash.merge": "^4.6.2", "needle": "^2.5.2", "stream-parser": "~0.3.1" } }, "sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ=="], + + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + + "prr": ["prr@1.0.1", "", {}, "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw=="], + + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + + "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], + + "rollup": ["rollup@4.63.5", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.63.5", "@rollup/rollup-android-arm64": "4.63.5", "@rollup/rollup-darwin-arm64": "4.63.5", "@rollup/rollup-darwin-x64": "4.63.5", "@rollup/rollup-freebsd-arm64": "4.63.5", "@rollup/rollup-freebsd-x64": "4.63.5", "@rollup/rollup-linux-arm-gnueabihf": "4.63.5", "@rollup/rollup-linux-arm-musleabihf": "4.63.5", "@rollup/rollup-linux-arm64-gnu": "4.63.5", "@rollup/rollup-linux-arm64-musl": "4.63.5", "@rollup/rollup-linux-loong64-gnu": "4.63.5", "@rollup/rollup-linux-loong64-musl": "4.63.5", "@rollup/rollup-linux-ppc64-gnu": "4.63.5", "@rollup/rollup-linux-ppc64-musl": "4.63.5", "@rollup/rollup-linux-riscv64-gnu": "4.63.5", "@rollup/rollup-linux-riscv64-musl": "4.63.5", "@rollup/rollup-linux-s390x-gnu": "4.63.5", "@rollup/rollup-linux-x64-gnu": "4.63.5", "@rollup/rollup-linux-x64-musl": "4.63.5", "@rollup/rollup-openbsd-x64": "4.63.5", "@rollup/rollup-openharmony-arm64": "4.63.5", "@rollup/rollup-win32-arm64-msvc": "4.63.5", "@rollup/rollup-win32-ia32-msvc": "4.63.5", "@rollup/rollup-win32-x64-gnu": "4.63.5", "@rollup/rollup-win32-x64-msvc": "4.63.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw=="], + + "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], + + "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sax": ["sax@1.6.1", "", {}, "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="], + + "search-insights": ["search-insights@2.17.3", "", {}, "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ=="], + + "shiki": ["shiki@2.5.0", "", { "dependencies": { "@shikijs/core": "2.5.0", "@shikijs/engine-javascript": "2.5.0", "@shikijs/engine-oniguruma": "2.5.0", "@shikijs/langs": "2.5.0", "@shikijs/themes": "2.5.0", "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="], + + "stream-parser": ["stream-parser@0.3.1", "", { "dependencies": { "debug": "2" } }, "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ=="], + + "strictdom": ["strictdom@1.0.1", "", {}, "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "stylis": ["stylis@4.4.0", "", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], + + "superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="], + + "tabbable": ["tabbable@6.5.0", "", {}, "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA=="], + + "tinyexec": ["tinyexec@1.3.1", "", {}, "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "uuid": ["uuid@14.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + + "vitepress": ["vitepress@1.6.4", "", { "dependencies": { "@docsearch/css": "3.8.2", "@docsearch/js": "3.8.2", "@iconify-json/simple-icons": "^1.2.21", "@shikijs/core": "^2.1.0", "@shikijs/transformers": "^2.1.0", "@shikijs/types": "^2.1.0", "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^5.2.1", "@vue/devtools-api": "^7.7.0", "@vue/shared": "^3.5.13", "@vueuse/core": "^12.4.0", "@vueuse/integrations": "^12.4.0", "focus-trap": "^7.6.4", "mark.js": "8.11.1", "minisearch": "^7.1.1", "shiki": "^2.1.0", "vite": "^5.4.14", "vue": "^3.5.13" }, "peerDependencies": { "markdown-it-mathjax3": "^4", "postcss": "^8" }, "optionalPeers": ["markdown-it-mathjax3", "postcss"], "bin": { "vitepress": "bin/vitepress.js" } }, "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg=="], + + "vitepress-plugin-mermaid": ["vitepress-plugin-mermaid@2.0.17", "", { "optionalDependencies": { "@mermaid-js/mermaid-mindmap": "^9.3.0" }, "peerDependencies": { "mermaid": "10 || 11", "vitepress": "^1.0.0 || ^1.0.0-alpha" } }, "sha512-IUzYpwf61GC6k0XzfmAmNrLvMi9TRrVRMsUyCA8KNXhg/mQ1VqWnO0/tBVPiX5UoKF1mDUwqn5QV4qAJl6JnUg=="], + + "vue": ["vue@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/compiler-sfc": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/server-renderer": "3.5.43", "@vue/shared": "3.5.43" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@mermaid-js/mermaid-mindmap/@braintree/sanitize-url": ["@braintree/sanitize-url@6.0.4", "", {}, "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="], + + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], + + "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], + + "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + + "probe-image-size/needle": ["needle@2.9.1", "", { "dependencies": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" }, "bin": { "needle": "./bin/needle" } }, "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ=="], + + "stream-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "superjson/copy-anything": ["copy-anything@4.1.1", "", {}, "sha512-AoT6Imdr98feSpFfmFwTFN73ccdr7uFPf27cBCgYvyyRyn1BzLRxMvrHNmwXO5LJMddRy4Rdhw2b1h7vSMKsEw=="], + + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], + + "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + + "probe-image-size/needle/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + + "stream-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + } +} diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 0000000..8c2fa62 --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,267 @@ +import { withMermaid } from 'vitepress-plugin-mermaid' +import type { DefaultTheme } from 'vitepress/theme' + +const gettingStartedSidebar: DefaultTheme.SidebarItem[] = [ + { + text: '开始使用', + items: [ + { text: '认识 Felis', link: '/' }, + { text: '安装与部署', link: '/guide/deployment' }, + { text: '管理服务器', link: '/guide/servers' }, + ], + }, + { + text: '项目资料', + collapsed: true, + items: [ + { text: '开源协议', link: '/reference/license' }, + { text: '项目说明', link: '/reference/readme-en' }, + ], + }, +] + +const operationsSidebar: DefaultTheme.SidebarItem[] = [ + { + text: '部署与运维', + items: [ + { text: '部署架构', link: '/reference/architecture' }, + { text: '运维手册', link: '/operations/' }, + { text: '备份与恢复', link: '/operations/backup' }, + { text: '故障排查', link: '/operations/troubleshooting' }, + { text: '多机部署(实验性)', link: '/guide/distributed' }, + ], + }, +] + +const developmentSidebar: DefaultTheme.SidebarItem[] = [ + { + text: '开发参考', + items: [ + { text: '从源码构建', link: '/guide/building' }, + { text: '贡献指南', link: '/reference/contributing' }, + { text: 'API 定义', link: '/reference/api' }, + { text: '时序图', link: '/reference/sequence-diagrams' }, + { text: '服务端插件', link: '/reference/plugins' }, + { text: '集成边界', link: '/reference/deferred-seams' }, + ], + }, + { + text: '镜像参考', + collapsed: true, + items: [ + { text: '大厅镜像', link: '/reference/lobby' }, + { text: '登录服镜像', link: '/reference/limbo' }, + ], + }, +] + +const sidebar: Record = { + '/guide/distributed': operationsSidebar, + '/guide/building': developmentSidebar, + '/reference/architecture': operationsSidebar, + '/reference/license': gettingStartedSidebar, + '/reference/readme-en': gettingStartedSidebar, + '/operations/': operationsSidebar, + '/reference/': developmentSidebar, + '/': gettingStartedSidebar, +} + +const englishLabels: Record = { + '开始使用': 'Getting started', + '认识 Felis': 'Introduction to Felis', + '安装与部署': 'Installation and deployment', + '管理服务器': 'Managing servers', + '项目资料': 'Project information', + '开源协议': 'License', + '项目说明': 'Project README', + '部署与运维': 'Deployment and operations', + '部署架构': 'Deployment architecture', + '运维手册': 'Operations guide', + '备份与恢复': 'Backup and restore', + '故障排查': 'Troubleshooting', + '多机部署(实验性)': 'Multi-node deployment (experimental)', + '开发参考': 'Developer reference', + '从源码构建': 'Build from source', + '贡献指南': 'Contributing', + 'API 定义': 'API definition', + '时序图': 'Sequence diagrams', + '服务端插件': 'Server-side plugins', + '集成边界': 'Integration boundaries', + '镜像参考': 'Image reference', + '大厅镜像': 'Lobby image', + '登录服镜像': 'Login server image', +} + +function englishSidebar(items: DefaultTheme.SidebarItem[]): DefaultTheme.SidebarItem[] { + return items.map(item => ({ + ...item, + text: item.text && englishLabels[item.text], + items: item.items && englishSidebar(item.items), + })) +} + +export default withMermaid({ + title: 'Felis 文档', + titleTemplate: ':title · Felis 文档', + description: 'Felis 文档:Kubernetes 驱动的 Minecraft 服务器托管,从部署到唤醒、备份与恢复。', + lang: 'zh-CN', + locales: { + root: { label: '简体中文', lang: 'zh-CN' }, + en: { + label: 'English', + lang: 'en', + title: 'Felis Docs', + titleTemplate: ':title · Felis Docs', + description: 'Documentation for Felis, a Kubernetes-driven Minecraft server hosting platform.', + themeConfig: { + nav: [ + { + text: 'Getting started', + link: '/en/', + activeMatch: '^/en/(?:$|guide/(?:deployment|servers)$|reference/(?:license|readme-en)$)', + }, + { + text: 'Deployment and operations', + link: '/en/operations/', + activeMatch: '^/en/(?:operations/|guide/distributed$|reference/architecture$)', + }, + { + text: 'Developer reference', + link: '/en/reference/contributing', + activeMatch: '^/en/(?:guide/building$|reference/(?!(?:architecture|license|readme-en)$))', + }, + ], + sidebar: Object.fromEntries(Object.entries(sidebar).map(([path, items]) => [ + '/en' + path, + { base: '/en', items: englishSidebar(items) }, + ])), + outline: { level: [2, 3], label: 'On this page' }, + docFooter: { prev: 'Previous page', next: 'Next page' }, + editLink: { + pattern: 'https://github.com/FelisMC/docs/edit/main/docs/:path', + text: 'Edit this page on GitHub', + }, + lastUpdated: { text: 'Last updated', formatOptions: { dateStyle: 'medium' } }, + sidebarMenuLabel: 'Menu', + returnToTopLabel: 'Back to top', + langMenuLabel: 'Change language', + skipToContentLabel: 'Skip to content', + darkModeSwitchLabel: 'Theme', + lightModeSwitchTitle: 'Switch to light theme', + darkModeSwitchTitle: 'Switch to dark theme', + notFound: { + title: 'Page not found', + quote: 'The page you are looking for does not exist.', + linkLabel: 'Go to the home page', + linkText: 'Back to documentation', + }, + footer: { copyright: 'FelisMC · Built with VitePress' }, + }, + }, + }, + base: process.env.DOCS_BASE || '/', + appearance: true, + lastUpdated: true, + markdown: { + config(md) { + const fence = md.renderer.rules.fence! + md.renderer.rules.fence = (...args) => fence(...args).replace( + 'title="Copy Code"', + `title="${args[3].localeIndex === 'en' ? 'Copy code' : '复制代码'}"`, + ) + }, + }, + mermaid: { + sequence: { useMaxWidth: false, wrap: true }, + }, + head: [ + ['meta', { name: 'theme-color', content: '#ffffff', media: '(prefers-color-scheme: light)' }], + ['meta', { name: 'theme-color', content: '#131610', media: '(prefers-color-scheme: dark)' }], + ], + vite: { + optimizeDeps: { + exclude: ['@nolebase/vitepress-plugin-enhanced-readabilities/client', 'vitepress', '@nolebase/ui'], + }, + ssr: { + noExternal: [ + '@nolebase/vitepress-plugin-enhanced-readabilities', + '@nolebase/vitepress-plugin-highlight-targeted-heading', + '@nolebase/ui', + ], + }, + }, + themeConfig: { + logo: { src: '/felis-logo.png', alt: 'Felis' }, + nav: [ + { + text: '开始使用', + link: '/', + activeMatch: '^/(?:$|guide/(?:deployment|servers)$|reference/(?:license|readme-en)$)', + }, + { + text: '部署与运维', + link: '/operations/', + activeMatch: '^/(?:operations/|guide/distributed$|reference/architecture$)', + }, + { + text: '开发参考', + link: '/reference/contributing', + activeMatch: '^/(?:guide/building$|reference/(?!(?:architecture|license|readme-en)$))', + }, + ], + sidebar, + socialLinks: [{ icon: 'github', link: 'https://github.com/FelisMC/docs' }], + search: { + provider: 'local', + options: { + locales: { + en: { + translations: { + button: { buttonText: 'Search docs', buttonAriaLabel: 'Search docs' }, + modal: { + displayDetails: 'Display detailed list', + resetButtonTitle: 'Reset search', + backButtonTitle: 'Close search', + noResultsText: 'No results found', + footer: { selectText: 'Select', navigateText: 'Navigate', closeText: 'Close' }, + }, + }, + }, + }, + translations: { + button: { buttonText: '搜索文档', buttonAriaLabel: '搜索文档' }, + modal: { + displayDetails: '显示详细列表', + resetButtonTitle: '清除搜索', + backButtonTitle: '关闭搜索', + noResultsText: '没有找到相关内容', + footer: { selectText: '选择', navigateText: '切换', closeText: '关闭' }, + }, + }, + }, + }, + outline: { level: [2, 3], label: '本页内容' }, + docFooter: { prev: '上一页', next: '下一页' }, + editLink: { + pattern: 'https://github.com/FelisMC/docs/edit/main/docs/:path', + text: '在 GitHub 上编辑此页', + }, + lastUpdated: { text: '最后更新', formatOptions: { dateStyle: 'medium' } }, + sidebarMenuLabel: '目录', + returnToTopLabel: '回到顶部', + langMenuLabel: '切换语言', + skipToContentLabel: '跳至正文', + darkModeSwitchLabel: '主题', + lightModeSwitchTitle: '切换到浅色模式', + darkModeSwitchTitle: '切换到深色模式', + notFound: { + title: '页面不存在', + quote: '你访问的页面不存在或已移动。', + linkLabel: '返回文档首页', + linkText: '返回文档', + }, + footer: { + copyright: 'FelisMC · 使用 VitePress 构建', + }, + }, +}) diff --git a/docs/.vitepress/theme/index.mts b/docs/.vitepress/theme/index.mts new file mode 100644 index 0000000..8472b19 --- /dev/null +++ b/docs/.vitepress/theme/index.mts @@ -0,0 +1,24 @@ +import { h } from 'vue' +import DefaultTheme from 'vitepress/theme' +import type { Theme } from 'vitepress' +import { + InjectionKey, + NolebaseEnhancedReadabilitiesMenu, + NolebaseEnhancedReadabilitiesScreenMenu, +} from '@nolebase/vitepress-plugin-enhanced-readabilities/client' +import { NolebaseHighlightTargetedHeading } from '@nolebase/vitepress-plugin-highlight-targeted-heading/client' +import '@nolebase/vitepress-plugin-enhanced-readabilities/client/style.css' +import '@nolebase/vitepress-plugin-highlight-targeted-heading/client/style.css' +import './style.css' + +export default { + extends: DefaultTheme, + enhanceApp({ app }) { + app.provide(InjectionKey, { spotlight: { defaultToggle: true } }) + }, + Layout: () => h(DefaultTheme.Layout, null, { + 'nav-bar-content-after': () => h(NolebaseEnhancedReadabilitiesMenu), + 'nav-screen-content-after': () => h(NolebaseEnhancedReadabilitiesScreenMenu), + 'layout-top': () => h(NolebaseHighlightTargetedHeading), + }), +} satisfies Theme diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 0000000..ce6f170 --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,68 @@ +:root { + --felis-lime: #c7e86b; + --felis-lime-hover: #b7da54; + --vp-c-brand-1: #587f00; + --vp-c-brand-2: #7fa91c; + --vp-c-brand-3: #a6ce39; + --vp-c-brand-soft: rgb(199 232 107 / 26%); + --vp-c-bg: #ffffff; + --vp-c-bg-alt: #f5fbe8; + --vp-c-bg-soft: #f4fae8; + --vp-c-bg-elv: #ffffff; + --vp-sidebar-bg-color: var(--vp-c-bg); + --vp-c-text-1: #19220d; + --vp-c-text-2: #4f5d3c; + --vp-c-text-3: #6e7c5b; + --vp-c-divider: #e3eccf; + --vp-custom-block-warning-bg: #fff8d0; + --vp-button-brand-bg: var(--felis-lime); + --vp-button-brand-text: #263015; + --vp-button-brand-hover-bg: var(--felis-lime-hover); + --vp-button-brand-hover-text: #263015; + --vp-button-brand-active-bg: #acca48; + --vp-button-brand-active-text: #263015; + --vp-button-alt-bg: transparent; + --vp-button-alt-border: var(--vp-c-divider); + --vp-button-alt-hover-border: var(--vp-c-text-3); + --vp-font-family-base: 'Inter', 'SF Pro Display', 'PingFang SC', 'Microsoft YaHei', sans-serif; + --vp-font-family-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace; + --vp-nav-height: 76px; +} + +.dark { + --vp-c-brand-1: var(--felis-lime); + --vp-c-brand-2: #d5ee93; + --vp-c-brand-3: #afd04f; + --vp-c-brand-soft: rgb(199 232 107 / 6%); + --vp-c-bg: #131610; + --vp-c-bg-alt: #181c14; + --vp-c-bg-soft: #1c2018; + --vp-c-bg-elv: #20251b; + --vp-c-text-1: #edf0e6; + --vp-c-text-2: #a6ad9b; + --vp-c-text-3: #808875; + --vp-c-divider: #2a3024; +} + +body { -webkit-font-smoothing: antialiased; } +html[lang='zh-CN'] { --vp-code-copy-copied-text-content: '已复制'; } +::selection { color: var(--vp-c-text-1); background: var(--vp-c-brand-soft); } +:focus-visible { outline: 2px solid var(--vp-c-brand-1); outline-offset: 4px; } +.VPNavBarTitle .title { font-size: 21px; font-weight: 650; letter-spacing: -0.6px; } +.VPNavBarTitle .logo { width: 36px; height: 36px; } +.VPNavBarMenuLink { font-size: 13px; } +.VPNavBarSearch .DocSearch-Button { border: 1px solid var(--vp-c-divider); border-radius: 5px; background: transparent; } +.VPSocialLinks.VPNavBarSocialLinks.social-links { margin-right: 0; } +.VPSidebarItem.is-active > .item { padding: 0 10px; border-radius: 5px; background: var(--felis-lime); } +.VPSidebarItem.is-active > .item > .link .text { color: #263b08 !important; } +.vp-doc h1 { font-size: 34px; line-height: 1.35; font-weight: 600; letter-spacing: -0.04em; } +.vp-doc h2 { font-weight: 550; letter-spacing: -0.02em; } +.vp-doc p, .vp-doc li { line-height: 1.9; } +.vp-doc div[class*='language-'] { border: 1px solid var(--vp-c-divider); border-radius: 5px; } +.vp-doc .custom-block { border-radius: 5px; } +.vp-doc .mermaid { overflow-x: auto; } +.VPDocAsideOutline .outline-title { font-weight: 500; } + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { scroll-behavior: auto !important; animation: none !important; transition: none !important; } +} diff --git a/docs/en/guide/building.md b/docs/en/guide/building.md new file mode 100644 index 0000000..a7d34ba --- /dev/null +++ b/docs/en/guide/building.md @@ -0,0 +1,25 @@ +--- +title: Build from source +--- + +# Build from source {#从源码构建} + +Felis is built with Go and Node.js: + +```bash +# Backend (Go 1.26+) +go build -o felis ./cmd/felis + +# Frontend (Node.js 22+) +cd panel +npm ci +npm run build + +# Docker image +docker build -t felis:custom . +``` + +See [Contributing](/en/reference/contributing) for the development environment, tests and plugin builds. +--- + +Source: [README_EN.md](https://github.com/FelisMC/Felis/blob/main/README_EN.md), [CONTRIBUTING.md](https://github.com/FelisMC/Felis/blob/main/CONTRIBUTING.md). diff --git a/docs/en/guide/deployment.md b/docs/en/guide/deployment.md new file mode 100644 index 0000000..3ba15b9 --- /dev/null +++ b/docs/en/guide/deployment.md @@ -0,0 +1,40 @@ +--- +title: Installation and deployment +--- + +# Installation and deployment {#安装与部署} + +> [!CAUTION] +> **This project is still in early development. Do not use it in production. The FelisMC team accepts no civil or criminal liability for problems arising from its use.** + +On a prepared Linux host, run: + +```bash +curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap.sh | sudo bash +``` + +The script installs K3s, deploys PostgreSQL and the control plane inside it, and launches a setup wizard. When setup completes, open the configured domain in a browser to reach the control panel. + +* **Setup wizard**: The wizard first binds the platform Owner: join the address it shows in Minecraft Java Edition, then enter the 8-character link code that the login server displays (valid for 10 minutes). The step can be skipped and completed later by running `sudo felis setup` again; until an Owner is bound, nobody can sign in to the control panel, and the sign-in page states this together with the binding steps and the address to join. The installer launches the wizard automatically only on an interactive terminal; when output is redirected to a log or the install runs under cloud-init, run `sudo felis setup` after it finishes. Setting `FELIS_NO_SETUP=1` makes the installer end at its summary. + +* **Supported hosts**: CentOS Stream 9 (aarch64) is verified on physical hardware; Ubuntu 24.04 (x86_64) is tested in CI on every push with a fresh install, a rerun, an upgrade and the install command above (see [operations §1](/en/operations/#_1-supported-hosts)). + +* **Preflight checks**: Before modifying the host, the installer checks memory, disk, ports, network range conflicts, existing Kubernetes installations and outbound connectivity. If any check fails, it lists all problems and exits, leaving the host unchanged (see [operations §1](/en/operations/#_1-supported-hosts) for the checks). + +* **Upgrading**: Rerun the install command to upgrade felis-api to a newer version; `felis setup` only uses the binary already installed on the host and cannot upgrade it. A rerun keeps the installed root domain, and the release channel must be specified again: hosts that follow the main branch must also set `export FELIS_VERSION_BOOTSTRAP=dev`. A PostgreSQL instance installed on the host by an earlier release is migrated into K3s during the rerun; the original instance on the host is stopped and retained for rollback (see [operations §4](/en/operations/#_4-upgrading-the-pieces-around-felis)). + +
+Installation sources and restricted networks +
+ +A release installation takes the binary, all images and the Velocity plugin from the release assets prebuilt in CI, verifying each against `SHA256SUMS` before import. The host requires no Docker, Gradle or Go, and no access to Docker Hub. If an asset is missing or fails verification, only that image falls back to a local build, and the installer prints a notice (see [troubleshooting §15c](/en/operations/troubleshooting#_15c-the-installer-builds-on-the-host-although-it-installs-a-release)). + +The assets can also be copied to the host in advance and installed with `FELIS_ARTIFACT_DIR=`; the Felis binary, images and plugin are then read from that directory. k3s and its images, the JRE, cloudflared, Velocity and the Via plugins are still downloaded from GitHub and PaperMC; hosts with SELinux enabled, such as RHEL, Fedora and openSUSE Leap, additionally install k3s-selinux from rpm.rancher.io; system packages come from the distribution's repositories. + +A host with restricted outbound access must therefore allow HTTPS to these addresses or set `https_proxy`. Preflight probes each address before changing the host. Fully offline installation is not yet supported (see [operations §1](/en/operations/#_1-supported-hosts) for the address list). + +
+ +--- + +Source: [README_EN.md](https://github.com/FelisMC/Felis/blob/main/README_EN.md). diff --git a/docs/en/guide/distributed.md b/docs/en/guide/distributed.md new file mode 100644 index 0000000..5b5f172 --- /dev/null +++ b/docs/en/guide/distributed.md @@ -0,0 +1,126 @@ +--- +title: Multi-node deployment +--- + +# A controller with multiple workers {#a-主控与多机-worker} + +Distributed mode is disabled by default. A runs the sole Felis API/operator, k3s server, PostgreSQL, registry, archive service and system servers; Velocity remains a systemd service on A. Nodes B, C and others run only k3s-agent/containerd, game Pods and maintenance Jobs created by A. Every node must use the same architecture and k3s version as A; administrators must trust and maintain the hosts. + +## Prepare A first {#先准备-a} + +Stop the servers during a maintenance window, back up PostgreSQL using the existing database backup procedure, and keep an offline copy of the k3s state, server token and current installation configuration. For SQLite-backed k3s, the state directory is `/var/lib/rancher/k3s/server/db`; for another datastore, follow its own backup procedure. Do not copy these files to workers. + +Record A's existing node name and keep it in subsequent installations. Pin the current control workloads to A before enabling WireGuard; existing game PVCs are not migrated. + +```bash +# Run as root on A; replace the node name and every static node address. +A_NODE=existing-node-name +PEERS=192.0.2.10/32,192.0.2.11/32,192.0.2.12/32 +k3s kubectl label node "$A_NODE" \ + felis.node-restriction.kubernetes.io/identity="$A_NODE" \ + felis.node-restriction.kubernetes.io/role=controller --overwrite + +# Use the actual Deployment names and protected identity labels throughout. +for d in felis-api felis-operator felis-postgres registry; do + k3s kubectl -n felis patch deployment "$d" --type merge \ + -p "{\"spec\":{\"template\":{\"spec\":{\"nodeSelector\":{\"felis.node-restriction.kubernetes.io/identity\":\"$A_NODE\"}}}}}" +done +``` + +Rerun an installer that includes this feature on A with `FELIS_DISTRIBUTED=1`, `FELIS_NODE_EXTERNAL_IP=` and `FELIS_PEER_CIDRS="$PEERS"`, retaining the existing installation parameters. This enables `wireguard-native`, `flannel-external-ip`, NodeRestriction and a separate agent token, and installs the archive service, minimal RBAC and host isolation rules. Changing WireGuard requires a maintenance window with the servers stopped. Update existing workers' peer lists in advance as well. + +Pushing to main alone does not publish a release. Until release assets contain these changes, run the following as root in the updated source checkout to build explicitly from main, retaining all other existing installation parameters. Updating only the installer while keeping the default release channel can download an older binary without distributed commands. + +```bash +FELIS_REF=main FELIS_DISTRIBUTED=1 \ + FELIS_NODE_EXTERNAL_IP= \ + FELIS_PEER_CIDRS="$PEERS" bash deploy/bootstrap.sh +``` + +When generating manifests directly, add: + +```bash +felis manifests --felis-image \ + --distributed --controller-node "$A_NODE" \ + --egress-probe felis-api.felis.svc:443 \ + --velocity-cidr \ + --archive-local-path --backup-pvc felis-backups +``` + +Retain the other existing parameters. The installer also pins CoreDNS and local-path-provisioner to A; for a manual deployment, give these Deployments A's protected node selector too. Manual deployments must store the same random key in the `felis-archive-key` Secret in both the `felis` and `minecraft` namespaces (field `key`, at least 32 characters). Only the API, Reaper and archive service receive the key; the operator does not. The archive PVC, registry and database stay on A. + +## Join B, then C {#接入-b-再接入-c} + +First run `felis node firewall --peers "$PEERS" --controller-ip ` on every existing node to update the complete peer list; add `--controller` on A. Only exact `/32` or `/128` addresses are allowed. Do not use an entire node or Pod range as the Velocity or registry source. Allow WireGuard UDP 51820–51821 between static public node addresses, and allow k3s 6443 only from known peers. + +```bash +# A: create a separate token for each worker, valid for 10 minutes by default. +# The output file is root-only; the token is not printed. +felis node token --name b --ttl 10m --out /root/b.bootstrap +k3s kubectl -n felis get svc registry +# Transfer this file and the same-version felis binary to B over trusted SSH/SCP. + +# B needs no server token, admin kubeconfig, database or registry write credentials. +felis node join --name b --server https://:6443 \ + --external-ip --token-file /root/b.bootstrap \ + --registry-ip --peers "$PEERS" + +# A: SSH uses existing host-key verification; the target account needs sudo -n. +felis node approve --name b --ssh-target \ + --image +felis node list +``` + +Installing a worker does not install the database, Velocity, API or operator, and does not delete local volumes or node-password. A repeated installation rejects renaming the node or switching clusters. The mirror uses the registry ClusterIP, disables fallback to the default image source, and preserves the original image references. + +Before approval, a worker has a `NoSchedule` quarantine taint and no protected approved label. Approval checks the architecture, version, node availability, WireGuard and host isolation, verifies that kubelet cannot modify protected labels, deletes the specified image and performs a real `crictl pull`. It then creates temporary probes to test cross-node Services, positive reachability of control services, and denial paths for game-labelled Pods. A connects to each test Service from the host and reads the source address actually observed by the backend; only exact addresses belonging to A are written into game policies. Any failed check keeps the node quarantined. Approval deletes the specified cached image, so run it before the node has game workloads. + +Once approved, administrators can select the node when creating a server in the panel, or pass `nodeName` in the creation request. Ordinary server owners cannot select a node or specify a PVC. An unreachable node rejects new work; the operator removes the server Service's backends and Ready status, and Velocity follows its existing fallback flow without moving to another node. + +## Migrate a stopped server {#停服迁移} + +Stop the server in the panel and wait for `Stopped` and for the game Pod to exit. Open **Stopped migration** and select an online, approved target worker. The panel reads the latest operation from the CR's persistent record, so progress remains visible after a page refresh or an A restart. + +```bash +# Root operations on A; separate from the database's felis migrate command: +felis server-migrate start --name survival --target-node c +felis server-migrate status --name survival +felis server-migrate retry --name survival --id +``` + +Administrator API: + +| Method | Path | Purpose | +| --- | --- | --- | +| GET | `/api/v1/nodes` | Execution node list | +| POST | `/api/v1/servers/{name}/migrations` | `{ "targetNode": "c" }`; returns the operation and ID | +| GET | `/api/v1/servers/{name}/migrations` | Latest migration record | +| GET | `/api/v1/servers/{name}/migrations/{id}` | Current operation progress | +| POST | `/api/v1/servers/{name}/migrations/{id}/retry` | Retry the failed stage | + +Stages are `backing_up` → `restoring` → `switching` → `succeeded`. The lock is `migration@` and does not expire under the temporary maintenance lock's two-minute rule. Failure records the stage and reason while keeping the lock and stopped state. A repeated request for the same target returns the existing operation; other migrations are rejected. The restore Job checks the download's SHA-256 and reads back each restored file for verification. Only after success does a single optimistic-lock CR update switch the node, active PVC and progress. The stopped StatefulSet is rebuilt to use the new PVC; the CR, Service, ClusterIP, domain and ownership remain unchanged. + +A successful migration still does not start the server automatically. Inspect the target world and start it manually. The recorded `sourcePVC` is retained and is not removed by the reaper; an administrator must explicitly clean it up once it is no longer needed. On failure, do not manually delete migration annotations or locks. Resolve source-node loss, insufficient target disk space or transfer/verification errors, then retry. A committed switch does not automatically roll back to the source world. + +## Isolation and acceptance {#隔离与验收} + +Game processes keep the existing restrictions: non-root, no privilege escalation, drop ALL, no service-account token and no host namespaces. A maintenance Job mounts only one world PVC. Transfer Jobs can access only the archive service and DNS; file/export Jobs additionally access only the existing API transfer endpoint. The archive service has no database configuration or Kubernetes identity. A records success only after the complete archive has been written atomically; existing tarLocal paths, retention and off-site workflows remain in use. + +Host INPUT/FORWARD rules close the resident-node path, and raw PREROUTING closes worker NodePorts before DNAT; trusted control Pods on A can reach the apiserver from their exact addresses. Raw rules also deny new game-Pod connections to the host, preventing kube-router's early ACCEPT from bypassing filter rules; replies on established Velocity/RCON connections remain allowed. Rules are installed through systemd, with control-Pod addresses refreshed periodically. After changing node addresses, firewalls or CNI configuration, stop the servers and rerun approval checks before running untrusted code. The game egress gate checks both the allowed DNS TCP path and denial paths; in distributed mode, a timeout prevents startup. + +Acceptance must be completed on three Linux machines, A/B/C; single-node unit tests do not replace it: + +- Cross-node Velocity `ClusterIP:25565` connections and actual source addresses, RCON, idle shutdown and wake, and image pulls after cache removal. +- Backup and restore on B, B→C migration, unchanged Service IP/ownership, retained source PVC, and a target that remains stopped. +- Mutual exclusion of wake, file writes, export, reaping and duplicate requests during migration; reconciliation continues after A restarts. +- Source-node loss, interrupted transfers, full target/archive disks and read-back verification failures; confirm that the source world remains recoverable. +- Game-Pod requests to other servers, the controller, registry, host ports, kubelet and metadata are all denied; every denial target is reachable by a trusted positive probe. +- Expired, replayed, cross-server and wrong-operation archive tokens are rejected; kubelet cannot forge protected labels. + +Current local verification uses one existing ARM64 CentOS Stream 9 Felis VM and a temporary test program for the archive and migration logic. `deploy/test-node-firewall.sh` tests resident-node, NodePort DNAT and early-ACCEPT protection in isolated network namespaces without changing the VM's existing cluster network. Three-machine network acceptance remains mandatory before deployment. A remains a single point of failure for the control plane and public entry; the first version has no controller HA or automatic failover. + +Related upstream documentation: [k3s networking across public networks](https://docs.k3s.io/networking/distributed-multicloud), [time-limited bootstrap tokens](https://docs.k3s.io/cli/token), [NodeRestriction labels](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-isolationrestriction), [NetworkPolicy node boundaries](https://kubernetes.io/docs/concepts/services-networking/network-policies/). + +--- + +Source: [docs/distributed.md](https://github.com/FelisMC/Felis/blob/main/docs/distributed.md). diff --git a/docs/en/guide/servers.md b/docs/en/guide/servers.md new file mode 100644 index 0000000..01f3373 --- /dev/null +++ b/docs/en/guide/servers.md @@ -0,0 +1,39 @@ +--- +title: Managing servers +--- + +# Managing servers {#管理服务器} + +* **On-demand Start and Stop**: A server starts when a player connects to the proxy. The player waits in a queue during start-up and is transferred once the server is ready. Idle servers stop automatically to free memory. + +* **Web Dashboard**: Monitor server status, online players, and resource usage from your browser. + * Console (RCON), whitelist, bans, OPs and LuckPerms permissions + * File manager: create, delete, rename, chunked upload, download, and unzip while the server is stopped; also used to import worlds + * Schedules: run commands, restart, stop, start or back up by weekday and time zone, with an in-game warning to players beforehand + +* **Multi-core Support**: Compatible with Paper, Fabric, Forge, and NeoForge, accessed through a single Velocity proxy. + +* **Modpack Submission**: Players can upload modpacks. After admin approval, each modpack is built automatically and scanned with Trivy; the result is added to the image whitelist and can be selected as a server image. + +## 12. A configuration field seems to be ignored {#_12-a-configuration-field-seems-to-be-ignored} + +Every field below is read by a controller. What varies is the condition that +decides whether setting it does anything. + +| Field | What you might expect | Reality | +|---|---|---| +| `spec.startup.timeoutSeconds` | Start budget before `Failed` | Read by `startupTimedOut` (`reconciler.go:479`), called at `:126`. `0` or unset falls back to **300s**, then `markFailed("StartupTimeout")` | +| `spec.startup.readinessTimeoutSeconds` | First-probe budget | Read by `readinessTimedOut` (`reconciler.go:490`), called at `:157`. `0` or unset falls back to **300s**, then `markFailed("ReadinessTimeout")`. Not to be confused with the prober's own 5s dial timeout (`prober.go:45`) | +| `spec.idle.autoStopEnabled` | Auto-stop empty servers | Read at `reconciler.go:175` — but gated on `spec.rcon.enabled`, since the player tally comes from the RCON probe (§11) | +| `spec.idle.emptySecondsBeforeStop` | Empty grace period | Same branch. Must be `> 0`; the guard treats `0` as "off", not "stop immediately" | + +Both startup budgets are measured from the same `status.startRequestedAt`, so +`readinessTimeoutSeconds` is not a budget *after* pod readiness — it is a +deadline for the whole start, applied on the RCON-probe branch. + +--- + +See [Troubleshooting](/en/operations/troubleshooting) for startup, connection, idle shutdown and file operations. +--- + +Source: [README_EN.md](https://github.com/FelisMC/Felis/blob/main/README_EN.md), [docs/troubleshooting.md](https://github.com/FelisMC/Felis/blob/main/docs/troubleshooting.md). diff --git a/docs/en/index.md b/docs/en/index.md new file mode 100644 index 0000000..bb6f5a6 --- /dev/null +++ b/docs/en/index.md @@ -0,0 +1,70 @@ +--- +title: Introduction to Felis +--- + +# Introduction to Felis {#认识-felis} + +A Kubernetes-driven Minecraft server hosting platform. + +One command to deploy, with automatic lifecycle, backup, and security. + +> [!CAUTION] +> **This project is still in early development. Do not use it in production. The FelisMC team accepts no civil or criminal liability for problems arising from its use.** + +## Features {#特性} + +* **On-demand Start and Stop**: A server starts when a player connects to the proxy. The player waits in a queue during start-up and is transferred once the server is ready. Idle servers stop automatically to free memory. + +* **Web Dashboard**: Monitor server status, online players, and resource usage from your browser. + * Console (RCON), whitelist, bans, OPs and LuckPerms permissions + * File manager: create, delete, rename, chunked upload, download, and unzip while the server is stopped; also used to import worlds + * Schedules: run commands, restart, stop, start or back up by weekday and time zone, with an in-game warning to players beforehand + +* **Backup & Restore**: Enabled by default; the installer renders the archive PVC and its path. + * Manual backups: archive a server's entire data volume (`/data`, including worlds, configuration, plugins and mods) to the cluster's archive store, with rollback to any backup point + * Daily restore points: a server played that day gets a restore point once it stops; by default 7 are kept for up to 90 days, rotated separately from manual backups + * Download and export: download a single backup (with sha256 verification), delete a single backup, or export a whole world + * Off-site copy (optional): backups are encrypted on the host and synced to S3-compatible storage (AWS S3, Cloudflare R2, Backblaze B2, MinIO and others) + * Control-plane database: the database holding accounts, server ownership, quotas and the archive index is backed up daily and snapshotted before every upgrade migration; `felis db restore` rolls it back atomically, and the panel's Maintenance & Backups page shows the age of the latest backup (see [troubleshooting §16](/en/operations/troubleshooting#_16-control-plane-database-backups-and-disaster-recovery)) + +* **Diagnostics** + * `sudo felis status`: a summary of the node, control plane, game proxy, each server, backups and open alerts + * `sudo felis doctor`: runs all health checks and lists problems by area with troubleshooting pointers; sends no email + * `sudo felis support-bundle`: generates a redacted diagnostics archive to attach to support requests (see [troubleshooting §0](/en/operations/troubleshooting#_0-first-look-felis-status-felis-doctor-felis-support-bundle)) + * Watchdog: runs a check every 2 minutes and emails the platform owners when a problem persists; supports an external heartbeat monitor + +* **World Reaper** (optional): Worlds idle for more than 15 days are backed up and then removed to free disk space. Enable it by setting `FELIS_WORLDS_HOST_PATH` at install time (on k3s: `/var/lib/rancher/k3s/storage`); without it, no world is deleted. Expired backups are cleaned up daily regardless of this setting. + +* **Multi-core Support**: Compatible with Paper, Fabric, Forge, and NeoForge, accessed through a single Velocity proxy. + +* **Modpack Submission**: Players can upload modpacks. After admin approval, each modpack is built automatically and scanned with Trivy; the result is added to the image whitelist and can be selected as a server image. + +* **Security** + * Passkey login: passwordless authentication via fingerprint, face recognition, or hardware security keys + * Zero-trust access: panel traffic is protected by Cloudflare Access, and the internal API is not exposed to the internet + +* **Multi-node Deployment** (experimental, off by default): a single controller node issues all commands, the other nodes run game servers only, and a stopped server can be migrated to another node. Currently available only on the main branch; three-node acceptance testing is not yet complete (see [distributed mode](/en/guide/distributed)). + +## Read the documentation {#阅读文档} + +- [Installation and deployment](/en/guide/deployment) +- [Operations guide](/en/operations/) +- [Troubleshooting](/en/operations/troubleshooting) +- [Multi-node deployment](/en/guide/distributed) +- [Contributing](/en/reference/contributing) +- [Project README](/en/reference/readme-en) + +## Acknowledgements {#致谢} + +* [Kubernetes](https://kubernetes.io/): Container orchestration engine +* [K3s](https://k3s.io/): Lightweight Kubernetes distribution +* [Cloudflare Zero Trust](https://www.cloudflare.com/zero-trust/): Zero trust security infrastructure +* [PostgreSQL](https://www.postgresql.org/): Data persistence +* [React](https://react.dev/): User interface framework +* [Vite](https://vitejs.dev/): Frontend build tool +* [TailwindCSS](https://tailwindcss.com/): CSS framework +* [Bubble Tea](https://github.com/charmbracelet/bubbletea): TUI framework +* [Minecraft](https://www.minecraft.net/): The game this project serves +--- + +Source: [README_EN.md](https://github.com/FelisMC/Felis/blob/main/README_EN.md). diff --git a/docs/en/operations/backup.md b/docs/en/operations/backup.md new file mode 100644 index 0000000..dcdadaa --- /dev/null +++ b/docs/en/operations/backup.md @@ -0,0 +1,32 @@ +--- +title: Backup and restore +--- + +# Backup and restore {#备份与恢复} + +* **Backup & Restore**: Enabled by default; the installer renders the archive PVC and its path. + * Manual backups: archive a server's entire data volume (`/data`, including worlds, configuration, plugins and mods) to the cluster's archive store, with rollback to any backup point + * Daily restore points: a server played that day gets a restore point once it stops; by default 7 are kept for up to 90 days, rotated separately from manual backups + * Download and export: download a single backup (with sha256 verification), delete a single backup, or export a whole world + * Off-site copy (optional): backups are encrypted on the host and synced to S3-compatible storage (AWS S3, Cloudflare R2, Backblaze B2, MinIO and others) + * Control-plane database: the database holding accounts, server ownership, quotas and the archive index is backed up daily and snapshotted before every upgrade migration; `felis db restore` rolls it back atomically, and the panel's Maintenance & Backups page shows the age of the latest backup (see [troubleshooting §16](/en/operations/troubleshooting#_16-control-plane-database-backups-and-disaster-recovery)) + +## What a backup contains {#what-a-backup-contains} + +A backup tars the server's ENTIRE data volume — the same volume the server mounts +at `/data`: world folders, `server.properties`, plugins/mods, configs, jars, +libraries, logs and cache, not just the `world/` directory. A restore replaces the +volume's contents with the archive (files added since the backup are pruned), so a +restore also rolls config/plugin changes back. Sizes are dominated by +libraries/cache on stock Paper servers (~170MB for a fresh instance before any +world growth) — do not size the archive PVC as if only world data were stored. + +## Detailed procedures {#详细操作} + +- [World backups, downloads, exports and daily restore points](/en/operations/troubleshooting#_10-world-reaper-false-deletes-and-skipped-backups-spec-§18) +- [Control-plane database backups and disaster recovery](/en/operations/troubleshooting#_16-control-plane-database-backups-and-disaster-recovery) +- [Disaster recovery and planned migration](/en/operations/#_5-disaster-recovery) + +--- + +Source: [README_EN.md](https://github.com/FelisMC/Felis/blob/main/README_EN.md), [docs/troubleshooting.md](https://github.com/FelisMC/Felis/blob/main/docs/troubleshooting.md), [docs/operations.md](https://github.com/FelisMC/Felis/blob/main/docs/operations.md). diff --git a/docs/en/operations/index.md b/docs/en/operations/index.md new file mode 100644 index 0000000..9bbe96e --- /dev/null +++ b/docs/en/operations/index.md @@ -0,0 +1,853 @@ +--- +title: Operations guide +--- + +# Felis Operations Guide {#felis-operations-guide} + +What a Felis host needs, how big it should be, how to take Felis off it again, and where +the disaster-recovery procedures live. Fault-finding is in +[troubleshooting.md](/en/operations/troubleshooting); this document refers to its sections as §N. + +Evidence tags follow troubleshooting.md: **[VM-VERIFIED]** was run on a real host, +**[CI]** runs end to end on every push to main (`.github/workflows/e2e.yml`), +**[GO-TESTED]** / **[SH-TESTED]** is covered by `go test` or the shell tests under +`deploy/`, **[CODE-ONLY]** is what the code does and has not been run end to end. + +## 1. Supported hosts {#_1-supported-hosts} + +`deploy/bootstrap.sh` defaults to a single node. For the opt-in A controller / worker deployment, see [distributed.md](/en/guide/distributed). It needs systemd, root, and one of the +package managers below; everything else (k3s, the JRE, cloudflared, and Docker when an image +has to be built on the host; see "Where the binary and the images come from" below) it +installs. +PostgreSQL runs inside k3s as the `felis-postgres` Deployment, from the official image the +release pins by digest, with its data on the host in `/var/lib/felis/postgres`. + +| OS family | Package manager | Architectures | Status | +|---|---|---|---| +| CentOS Stream 9 (firewalld active, SELinux enforcing) | dnf | aarch64 | **[VM-VERIFIED]** fresh install from release assets and its rerun, upgrade from v0.1.0 (moving the database off the host PostgreSQL 13 into felis-postgres), uninstall and reinstall | +| Ubuntu 24.04 LTS | apt | x86_64 | **[CI]** fresh install and same-commit rerun from the pushed commit's release assets; the README's one-line install as a new host runs it (the newest release's own assets); upgrade from the newest release, installed from its assets by its own installer and seeded with rows in twelve tables, onto them, every seeded row read back unchanged; the on-host build weekly | +| RHEL / Rocky / Alma 9, Fedora | dnf | x86_64, aarch64 | [CODE-ONLY] same code path as CentOS Stream | +| Debian 12, other Ubuntu releases | apt | x86_64, aarch64 | [CODE-ONLY] | +| openSUSE Leap / Tumbleweed | zypper | x86_64, aarch64 | [CODE-ONLY] | +| Arch Linux | pacman | x86_64, aarch64 | [CODE-ONLY] | + +Pinned component versions (a fresh install gets exactly these; an installed k3s or +cloudflared is left as it is, see §4): + +| Component | Version | Where it is pinned | +|---|---|---| +| k3s | v1.36.4+k3s1 | `FELIS_K3S_VERSION` in `bootstrap.sh` | +| cloudflared | 2026.9.1 | `FELIS_CLOUDFLARED_VERSION`, sha256 per architecture | +| Temurin JRE (Velocity) | 25, patch build pinned | `FELIS_JRE_VERSION`, sha256 per architecture | +| Go (nano builds) | 1.26.8 | `GO_PINNED_VERSION`, sha256 per architecture | +| Minecraft / Limbo / Paper / Velocity / LuckPerms | `deploy/game-stack.lock` | §15b | +| PostgreSQL | 18.6, the official `postgres` image by digest | `POSTGRES_IMAGE` in `bootstrap.sh`, `defaultPostgresImage` in `internal/platform` | + +32-bit hosts are not supported: there is no k3s, JRE or Go build the installer will fetch +for them. + +Before it changes anything the installer checks the host and reports every problem at +once, then stops with nothing touched **[SH-TESTED]**: + +- the architecture, systemd as init, and the memory cgroup controller k3s needs; +- RAM: under 1.75 GiB is refused (a "2 GB" VPS passes), under 3.5 GiB is a warning; +- free disk on each filesystem it writes to, summed when they share one: on a bare host + about 17 GiB installing a release, 15 GiB from `FELIS_ARTIFACT_DIR` and 23 GiB when it + builds the images itself; 7 GiB for a rerun; a directory that already holds data + (Docker's cache, a reused k3s) counting at the rerun size; a filesystem that would end + over 85%, where k3s starts deleting cached images, is a warning; +- the ports it will listen on: the game port, the panel NodePort, k3s's 6443/6444 and + 10248–10259 and the registry's loopback 5000. A port held by the installer's own + proxy or k3s is a rerun and passes; +- another Kubernetes (kubelet, RKE2, k0s, MicroK8s) or a k3s agent on the host; +- the node address or a routed network inside k3s's `10.42.0.0/16` and `10.43.0.0/16` + (a Docker network there is the usual case); a wider route such as a `10.0.0.0/8` VPN + is a warning; +- HTTPS to the hosts it downloads from: GitHub and PaperMC's download API always, Docker + Hub when it builds images on the host, Rancher's RPM repository where k3s's installer + adds it (the list is under "Where the binary and the images come from"). A host counts + as reachable once a TLS handshake with it completes, and each gets three tries two + seconds apart. Installing a release, an + unreachable Docker Hub is a warning (it is needed only if an asset turns out unusable); + from `FELIS_ARTIFACT_DIR` it is not checked. + +`FELIS_PREFLIGHT=warn` reports the same problems as warnings and installs anyway, for a +host the checks misjudge. + +A host firewall is opened, never turned off **[SH-TESTED]**. With firewalld active the +installer adds the panel NodePort, the game port and 6443, and puts k3s's pod and service +ranges in the trusted zone. With ufw enabled (common on Ubuntu and Debian, and enabled in +the CI install **[CI]**) it admits `10.42.0.0/16` and `10.43.0.0/16`, the panel NodePort and +the game port, each rule commented `felis-…`; 6443 stays closed to the network, since pods +reach the API server from their own range. Felis-nano opens its port to +`FELIS_NANO_PROXY_CIDR` alone in either. `uninstall.sh` removes these again, the k3s ranges +only when k3s goes too. Any other firewall in front of the host must admit the same: +dropped pod traffic shows up as the first rollout timing out ("control-plane rollout did +not complete"). + +Two things the host must keep for as long as the install lives: + +- **Its address.** The install is bound to the IPv4 address it was made on (the k3s + node, the network policies, the panel certificate and the default nip.io domain all + carry it). Give the host a static address or a DHCP reservation before installing; + the installer warns when the address is a lease, and the watchdog reports + `host-address` when the host loses it (troubleshooting §13c). The k3s node name is + pinned at install time, so a hostname change is harmless. +- **A synchronized clock.** The installer turns NTP on (chrony where nothing else can) + and the watchdog reports a clock that stays unsynchronized. Allow outbound UDP 123, + or set `FELIS_MANAGE_TIME_SYNC=0` on a host whose clock is kept another way. + +The installer also makes the system journal persistent (capped at +`FELIS_JOURNAL_MAX_USE`, default 1G; `FELIS_MANAGE_JOURNAL=0` skips it) and writes the +admin kubeconfig `/etc/rancher/k3s/k3s.yaml` root-only: run `sudo k3s kubectl`. + +Single-node deployment remains the default. The opt-in [distributed mode](/en/guide/distributed) +keeps the sole API and operator on A and runs games on approved k3s agents. A world is +a ReadWriteOnce claim on its node's local-path storage; moving it requires an explicit +stopped migration through A's archive service. There is no automatic failover or +standby controller. An A restart pauses control operations until its workloads return; +a lost worker leaves its worlds on that node. Cross-node networking still requires the +three-machine acceptance described in the distributed runbook. + +### Where the binary and the images come from {#where-the-binary-and-the-images-come-from} + +A release install (the default channel, and the setup console) takes everything Felis +builds from that release's assets, each checked against the release's `SHA256SUMS` before +it is used: the `felis` binary, the control-plane image, the limbo, lobby and paper images, +the registry and PostgreSQL images (at the digests `bootstrap.sh` pins), and +`felis-velocity.jar`. The images go into k3s's containerd with `k3s ctr images import` and +from there into the in-cluster registry, so the host needs no Docker, Gradle, Go or Docker +Hub for them. k3s's own images come from k3s's GitHub release +(`k3s-airgap-images-.tar.zst`, checked against k3s's sha256 list) before k3s first +starts. An upgrade downloads only the image tars holding an image the host lacks; they wait +in `/var/lib/felis/artifacts` until the registry has the images, and are deleted then. +`deploy/build-release-artifacts.sh` documents every asset. The decisions are **[SH-TESTED]**. +Installing from the assets is **[VM-VERIFIED]** on CentOS Stream 9 aarch64 through +`FELIS_ARTIFACT_DIR`: a fresh install and an upgrade over a release that built on the host +pulled no image and built nothing, and a rerun imported and uploaded nothing. Downloading them from a +release is [SH-TESTED] until a release publishes assets. +The release is the newest one unless `FELIS_RELEASE=` names an earlier one, which +installs from that release's assets the same way: the way back after a bad upgrade +(troubleshooting §16, "Roll back an upgrade that broke the database"), with the installer +read at that tag. + +The installer builds on the host instead, installing Docker for it and stopping Docker once +the images are in the registry, when: + +- the source is not a release: `FELIS_VERSION_BOOTSTRAP=dev`, a pinned `FELIS_REF`, or + `FELIS_SKIP_FETCH`; +- `FELIS_GAME_STACK=latest`, for the login, lobby and paper images (the rest still come + from the release); +- the release publishes no `SHA256SUMS` (one cut before release assets existed, or still + uploading), or an asset is missing, fails its checksum or is malformed. Only that image is + built (the registry and PostgreSQL images are pulled from Docker Hub instead), and a + warning names it; troubleshooting §15c lists the messages. Each download is tried + three times first, and a host without the room for the build stops before installing + Docker (troubleshooting §15c). + +`FELIS_ARTIFACT_DIR=` installs from a directory instead of the release: a +release's assets downloaded there (every `felis-*` file and `SHA256SUMS`), or the directory +`deploy/build-release-artifacts.sh ` wrote. Nothing of Felis's own is +downloaded or built (except the game images under `FELIS_GAME_STACK=latest`, which no release +ships), so an asset the directory lacks, or one failing its checksum, stops the install. It +cannot be combined with `FELIS_REF` or `FELIS_SKIP_FETCH`, which name a source too. + +The rest of the host's software still downloads, so the host needs outbound HTTPS to these, +directly or through `https_proxy`. A host with no outbound access cannot be installed yet +**[SH-TESTED]**: + +| Host | What comes from it | +|---|---| +| `github.com`, and the githubusercontent.com hosts its release downloads redirect to | k3s and its images (`k3s-airgap-images-.tar.zst`), cloudflared, the Temurin JRE, ViaVersion, ViaBackwards and ViaRewind | +| `raw.githubusercontent.com` | k3s's install script, until k3s is installed | +| `rpm.rancher.io` | k3s-selinux, which k3s's install script adds on an SELinux host of the Red Hat or SUSE family (CentOS Stream, RHEL, Rocky, Alma, Fedora, openSUSE Leap), until k3s is installed | +| `fill-data.papermc.io` | the Velocity jar, unless `FELIS_VELOCITY_FORK_JAR` supplies one | +| the distribution's package mirrors | the base packages (CA certificates, OpenSSL, curl and tar where missing), and container-selinux beside k3s-selinux | + +Preflight probes each named host above before it changes anything and lists every one it +cannot reach in one refusal (`cannot reach … over HTTPS`); the package manager reports its +own mirrors. An override adds a host the download itself tries: `FELIS_JRE_VERSION` reads +`api.adoptium.net`, a `FELIS_VELOCITY_VERSION` other than the pinned one reads +`fill.papermc.io`, and `FELIS_GAME_STACK=latest` builds its images on the host from Docker +Hub (which preflight probes), PaperMC, Limbo's CI and LuckPerms. + +``` +# on a machine with access: the release's assets for the host's architecture +gh release download v1.4.0 --repo FelisMC/Felis --dir felis-v1.4.0 \ + --pattern 'felis-*linux-amd64*' --pattern felis-velocity.jar --pattern SHA256SUMS +# on the host, after copying the directory over +sudo FELIS_ARTIFACT_DIR=/root/felis-v1.4.0 bash bootstrap.sh +``` + +`SHA256SUMS` lists both architectures; the files of the other one may be left out. + +### While felis-api restarts {#while-felis-api-restarts} + +An installer rerun that changes felis-api, a node restart or a crashed pod takes the API +away until its new pod is ready: about 12 s on the reference VM (`kubectl rollout +restart` to Available). Its Deployment keeps one replica with the Recreate strategy, so +the old pod is gone before the new one starts. Two pods at once would be wrong for +felis-api: the uploads volume is ReadWriteOnce, a chunked upload is serialized inside the +process, and the build reconciler, restore settler, registry pruner, upload reapers and +audit retention run in-process without leader election, so each would run twice. During +the window: + +- Players already on a server stay there; game servers keep running. +- A player leaving the login gate or joining a server by its address is admitted when + felis-api confirmed their link within the last 10 minutes; anyone else is told login + verification is temporarily unavailable. +- The login gate retries a new login for up to 60 s and tells the player it is retrying, + so a restart shorter than that only delays the login. +- Wakes, stops, `/link` and the panel wait for the API. +- A Velocity restart in the window routes on + `/opt/felis/velocity/plugins/felis-link/last-servers.json`, the last server list the + API answered with, until a refresh succeeds (every 15 s). + +A longer outage reads like this in the logs [VM-VERIFIED]. The drill scaled felis-api +to 0 for about 8 minutes on the reference VM. + +- The proxy logged `server list refresh failed ... keeping current registrations` 11 s + in, then `still failing: 22 failed attempts over 304 s` at the 5-minute mark. +- The watchdog found `deployment/felis-api` critical on its first run after the scale. + It raised the alert on the first run past 5 minutes, at about 7 minutes; with no + `[smtp]` that is logged only (`journalctl -u felis-watchdog`). +- The proxy logged `server list refresh recovered after 32 failed attempts over 469 s` + as soon as the new pod was Available. + +## 2. Sizing {#_2-sizing} + +### What the platform itself uses {#what-the-platform-itself-uses} + +Measured on the verification host (4 vCPU, 5.5 GB RAM, 6 GB swap, CentOS Stream 9 +aarch64) on an idle network, as each process's proportional set size (PSS: a page shared +by several processes is split among them; `/proc//smaps_rollup`) **[VM-VERIFIED]**: + +| Process | Memory (PSS) | +|---|---| +| k3s (API server, controllers, scheduler, kubelet) | ~370 MiB | +| k3s's containerd and the pods' shims | ~170 MiB | +| CoreDNS and the local-path volume provisioner | ~105 MiB | +| Velocity (`-Xms16M -Xmx1G`, idle; it grows with players) | ~175 MiB | +| felis-api, felis-operator, registry gate | ~85 MiB together | +| Image registry | ~25 MiB | +| PostgreSQL (the felis-postgres pod) | ~40 MiB plus page cache | +| **Infrastructure total** | **~1 GB** | + +The installer runs k3s, and the containerd it starts, with Go's collector at half its +default heap growth (`GOGC=50`, in `/etc/systemd/system/k3s.service.d/50-felis.conf`): an +idle k3s holds about 150 MiB live and would otherwise let its heap reach twice that before +collecting. It saves about 70 MiB for about 2% of one core. An install from before this +picks it up on its next installer run, which restarts k3s; the pods keep running. + +The login (Limbo, pod limit 512 MiB, ~0.16 GB) and lobby (Paper, pod limit 1 GiB, +~0.7–0.85 GB) system servers come on top, and every game server adds the memory its owner +gave it: the pod's limit equals its request, and the JVM heap is derived from it (§1a). +Quotas cap it per user (panel → Admin → Quotas). + +A release install builds nothing (§1). When the installer builds on the host its peak is +the image builds (Docker plus a Gradle container). Afterwards it stops Docker, and Docker's +containerd when nothing else uses it, so that memory goes back to the servers; a Docker the +installer put there does not start at boot. On a host under 2 GB of RAM without swap it +adds a 2 GiB `/swapfile`. + +### Recommendations {#recommendations} + +| Concurrent players | Game servers running | CPU | RAM | `FELIS_VELOCITY_XMX` | +|---|---|---|---|---| +| up to 20 | 1–2 small | 2 vCPU | 4 GB + 2 GB swap | 1G (default) | +| up to 100 | 3–5 | 4 vCPU | 8–16 GB | 1G | +| up to 300 | 5–10 | 8 vCPU | 16–32 GB | 2G | +| 300+ | more | 8+ vCPU | 32 GB+ | 3G–4G | + +The player-count rows are planning figures, not measurements: a Minecraft server's cost +depends mostly on what its players do (view distance, redstone, mods). Size RAM as the +infrastructure's ~1 GB and the login and lobby servers' ~1 GB, plus the sum of the servers +you expect to run at once, then add a quarter for the page cache and PostgreSQL. Velocity +itself needs little per player; raise its heap when `journalctl -u felis-velocity` shows +long GC pauses or `OutOfMemoryError`. + +`FELIS_VELOCITY_XMX` (default `1G`, at least `256M`, written `M` or `G`) is read on +every installer run. Up to 1G the heap starts at 16M and the proxy runs the serial collector +and only the C1 compiler: its plugins hold about 50M live, so a collection takes +milliseconds, and compression and encryption run in Velocity's native library. Above 1G it +runs G1 from a 64M start, since a serial full collection over a large heap would stall +every player at once, and a periodic collection hands the growth back once players have +left. Changing it rewrites the unit, and the rerun restarts the proxy, which disconnects +everyone online; do it in a quiet hour **[VM-VERIFIED]**: + +``` +curl -fsSL /deploy/bootstrap.sh | sudo FELIS_VELOCITY_XMX=2G bash +``` + +### Disk {#disk} + +| What | Where | Size | +|---|---|---| +| Worlds | one volume per server under `/var/lib/rancher/k3s/storage` | what the world grows to | +| World archives | the `felis-backups` volume (`FELIS_BACKUP_STORAGE`, default 10Gi requested) | about one compressed world per backup kept | +| In-cluster registry | the `registry` volume (default 10Gi requested) | 2–3 GB for the stock images; grows with custom builds, pruned daily (§9) | +| k3s's containerd images | `/var/lib/rancher/k3s/agent/containerd` | 6–9 GB | +| Docker's images and build cache | `/var/lib/containerd` (Docker's containerd store), on a host that built its images (§1) | 5–10 GB after repeated upgrades | +| Release assets during an install | `/var/lib/felis/artifacts` | up to ~2 GB, deleted once the images are in the registry | +| Toolchains and sources | `/opt/felis` | ~2.5 GB | +| Database | `/var/lib/felis/postgres` (felis-postgres's cluster) | tens of MB; the audit log is most of it | +| Database bundles | `/var/lib/felis/db-backups` | a few MB each, 14 daily kept | + +k3s's local-path volumes do not enforce the requested sizes (§9), so every volume shares +the root filesystem. Give the host at least **40 GB**, and 60 GB or more once worlds and +custom images accumulate. The watchdog mails the owners when a watched filesystem passes +its threshold, and §13b covers a full disk. On a host that built its images, +`docker builder prune -af` (with Docker started) reclaims the build cache when space is +short; the next upgrade rebuilds it. + +### Growing the disk {#growing-the-disk} + +Everything above shares the root filesystem, so more room means a bigger root +filesystem. It grows in place, with everything running: enlarge the virtual disk at the +provider, then the partition and the filesystem on it. + +```bash +sudo felis backup-now -yes # a mistyped partition number is how a resize loses a disk +lsblk -f # which disk and partition hold /, and whether LVM sits on it +sudo growpart /dev/vda 3 # cloud-utils-growpart (RHEL) / cloud-guest-utils (Debian, Ubuntu) +# LVM (the RHEL-family default): +sudo pvresize /dev/vda3 +sudo lvextend -r -l +100%FREE /dev//root # -r grows the filesystem with it +# no LVM: +sudo xfs_growfs / # xfs +sudo resize2fs /dev/vda3 # ext4 +df -h / +``` + +`felis backup-now` (troubleshooting.md §10) archives every stopped world; add `-stop` to +include the running ones. + +### Moving the data to its own disk [VM-VERIFIED] {#moving-the-data-to-its-own-disk-vm-verified} + +The bulk lives under `/var/lib/rancher/k3s`: the worlds, the world archives, the registry +and the images. On a disk of its own it grows without touching the system, and a full +world store leaves the root filesystem alone. The database and its bundles +(`/var/lib/felis`) are small and stay on the root disk. The move takes the platform down +for the copy plus a minute or two: the drill copied 4.2 GB in 18 s, and felis-api answered +`/readyz` 14 s after k3s started on the new disk. + +1. Attach the disk and put a filesystem on it (the whole disk; `lsblk` shows it empty): + + ```bash + sudo mkfs.xfs /dev/vdb + U=$(sudo blkid -s UUID -o value /dev/vdb) + ``` + +2. Archive every world, stopping the servers so each one saves, and keep the watchdog + quiet for the next hour (the marker the installer writes: no mail, no failure pings + to the heartbeat, until the time in it): + + ```bash + sudo felis backup-now -yes -stop + sudo install -d -m 0755 /run/felis + echo $(( $(date +%s) + 3600 )) | sudo tee /run/felis/watchdog-quiet-until + ``` + +3. Stop k3s and copy: + + ```bash + sudo systemctl stop k3s + sudo /usr/local/bin/k3s-killall.sh # the containers k3s leaves running, and their mounts + sudo mkdir -p /mnt/felis-data + sudo mount UUID=$U /mnt/felis-data + sudo rsync -aHAX --numeric-ids /var/lib/rancher/k3s/ /mnt/felis-data/ + sudo umount /mnt/felis-data + ``` + + `-X` carries the SELinux labels k3s set itself. Leave `restorecon` out: it would reset + runc and the CNI binaries from `container_runtime_exec_t` to the policy default. + +4. Mount it in place, and tie k3s to the mount: + + ```bash + sudo mv /var/lib/rancher/k3s /var/lib/rancher/k3s.old + sudo mkdir /var/lib/rancher/k3s + echo "UUID=$U /var/lib/rancher/k3s xfs defaults,nofail 0 0" | sudo tee -a /etc/fstab + sudo mkdir -p /etc/systemd/system/k3s.service.d + printf '[Unit]\nRequiresMountsFor=/var/lib/rancher/k3s\n' | sudo tee /etc/systemd/system/k3s.service.d/data-disk.conf + sudo systemctl daemon-reload + sudo mount /var/lib/rancher/k3s + sudo systemctl start k3s + ``` + + The drop-in is what keeps the data safe: k3s started on the empty mount point creates + a new, empty cluster there. With it, a disk that does not come up fails the start with + `A dependency job for k3s.service failed`, and `nofail` keeps the host booting so you + can reach it. In the drill a detached disk left k3s inactive and the mount point empty; + reattached, `systemctl start k3s` mounted it and started. + +5. Check that `sudo k3s kubectl -n felis get pods` shows every pod ready and + `findmnt /var/lib/rancher/k3s` names the new disk, then start the servers from the + panel and `sudo rm /run/felis/watchdog-quiet-until`. Once the host has run a day, + `sudo rm -rf /var/lib/rancher/k3s.old` frees the root disk. + +The watchdog already watches `/var/lib/rancher/k3s` as a filesystem of its own (its +`-disk-paths`), so the new disk's fill level is mailed like the root's. + +## 3. Uninstall {#_3-uninstall} + +`deploy/uninstall.sh` takes off what the installer put on. It prints what it will remove +and asks before it starts (`--yes` skips the question) **[SH-TESTED] +[VM-VERIFIED]**: + +``` +curl -fsSL /deploy/uninstall.sh | sudo bash -s -- --yes # keep the data +curl -fsSL /deploy/uninstall.sh | sudo bash -s -- --purge # remove the data too +``` + +Both modes remove the `felis-*` systemd units and `cloudflared-felis.service`, the +Velocity user, `/opt/felis`, `/usr/local/bin/felis`, the release assets an interrupted +install left in `/var/lib/felis/artifacts`, the installer's cloudflared binary (unless +another unit runs it), the `felis_edge` nftables table (and `felis_postgres`, which +releases before the database moved into k3s loaded), the firewalld ports the installer +opened and its `felis-`-commented ufw rules. k3s goes with k3s's own `k3s-uninstall.sh` when the cluster holds nothing but +Felis's namespaces; when it runs anything else only `felis`, `minecraft`, `felis-build` +and the MinecraftServer CRD are deleted. +`--keep-k3s` and `--remove-k3s` override that choice. + +| | keep data (default) | `--purge` | +|---|---|---| +| Final database bundle | taken first (`felis db backup -label manual`); a failure stops the uninstall before anything is removed. `--no-backup` skips it | none | +| The database (`/var/lib/felis/postgres`) | kept; felis-postgres is stopped cleanly before k3s goes | deleted with `/var/lib/felis` | +| A host PostgreSQL an earlier release ran the database on | kept as it is: stopped after the move into k3s (below, §4), with its old copy of `felis` | its `felis` database and role are dropped (the server is started for that and stopped again), and `listen_addresses` and `pg_hba.conf` go back to how they were. Checked before anything is removed: a role that still owns another database (the `felis_pgint` the PG contract tests use, CONTRIBUTING.md) or holds grants elsewhere stops the purge up front with the list and the `ALTER DATABASE … OWNER TO postgres` to run | +| `/etc/felis` (secrets, `felis.toml`, `offsite.env`, the mail relay password and uploads bucket keys `felis setup` took, tunnel config) | kept; `bootstrap.done` and the per-run records go | deleted, with the tunnel's credentials file | +| `/var/lib/felis` (the database, its bundles) | kept | deleted | +| Worlds, archives, registry, uploads | moved to `/var/lib/felis/retained/k3s-storage-/` (with `--keep-k3s`: their volumes switch to `Retain` and stay in place) | deleted | +| Felis images, Docker build cache | kept | deleted | + +The two database rows are [SH-TESTED] (`deploy/uninstall_test.sh`); the VM runs above +predate felis-postgres. + +Neither mode removes packages (Docker, git, nftables, and the PostgreSQL server an earlier +release installed) or the swap file: other software may use them. On a host that should +end up bare: + +``` +sudo swapoff /swapfile && sudo rm /swapfile && sudo sed -i '\|^/swapfile |d' /etc/fstab +sudo dnf remove docker-ce docker-ce-cli containerd.io postgresql-server # or apt/zypper/pacman +``` + +The Cloudflare side outlives the host. After an uninstall that is final, delete the +tunnel (Zero Trust → Networks → Tunnels, or `cloudflared tunnel delete `), its +DNS records for the panel hostnames, and the Access application. + +### Reinstall on top of kept data {#reinstall-on-top-of-kept-data} + +A keep-data uninstall leaves everything a reinstall needs. The installer reuses +`/etc/felis/secrets.env`, so the database password and the forwarding and session +secrets are unchanged, and the installer migrates the kept database instead of creating +one **[VM-VERIFIED]** (with the host database of the releases before felis-postgres). +felis-postgres starts again on the cluster kept in `/var/lib/felis/postgres` [SH-TESTED]. + +Each step below was run on the reference VM after a keep-data uninstall, and the +restored worlds matched their kept `level.dat` checksums **[VM-VERIFIED]**. `kept` names +the directory the uninstall moved the volumes to: + +``` +kept="$(ls -d /var/lib/felis/retained/k3s-storage-* | tail -n 1)" +store=/var/lib/rancher/k3s/storage +``` + +1. Install as usual (`curl ... | sudo bash`). Name the same root domain if it was not + the `.nip.io` default: `felis.host.toml` is kept, and the installer reads the + domain from it. +2. Run `sudo felis setup`. It recreates the login and lobby servers; the Owner already + exists, so it opens on the status screen and you can quit there. +3. Put the image registry and the uploads back. They hold every custom server image + and uploaded file; without the registry, a restored server fails to pull its image. + + ``` + sudo k3s kubectl -n felis scale deploy/registry deploy/felis-api --replicas=0 + sudo k3s kubectl -n felis wait --for=delete pod -l app.kubernetes.io/component=registry --timeout=120s + sudo k3s kubectl -n felis wait --for=delete pod -l app.kubernetes.io/component=api --timeout=120s + sudo rsync -a --delete "$kept"/pvc-*_felis_registry/ "$(ls -d $store/pvc-*_felis_registry)"/ + sudo rsync -a --delete "$kept"/pvc-*_felis_felis-uploads/ "$(ls -d $store/pvc-*_felis_felis-uploads)"/ + sudo k3s kubectl -n felis scale deploy/registry deploy/felis-api --replicas=1 + ``` + + Then run the installer once more. It pushes this release's images over the older + copies the kept registry carried. +4. Bring the game servers back. The final bundle holds every MinecraftServer as it was; + the selector skips login and lobby, which step 2 created for this release: + + ``` + b="$(ls -t /var/lib/felis/db-backups/felis-db-*-manual.tar | head -n 1)" + tar -xOf "$b" k8s/minecraftservers.json \ + | sudo k3s kubectl apply -l '!felis.lolicon.best/system-role' -f - + ``` + +5. Put each world back. A server's volume exists once it has started once, so start it + from the panel, stop it again, and copy the kept world over the new one: + + ``` + s= + sudo rsync -a --delete "$kept"/pvc-*_minecraft_world-$s-0/ "$(ls -d $store/pvc-*_minecraft_world-$s-0)"/ + ``` + + Then start it. The lobby works the same way: stop it with + `sudo k3s kubectl -n minecraft patch minecraftserver lobby --type=merge -p '{"spec":{"desiredState":"Stopped"}}'`, + copy `world-lobby-0`, and patch it back to `Running`. +6. Bring the archives back so the panel's restore points work again. The archive volume + appears with the first backup, so back up any server from the panel first, then: + + ``` + sudo rsync -a "$kept"/pvc-*_minecraft_felis-backups/ "$(ls -d $store/pvc-*_minecraft_felis-backups)"/ + ``` + + With an off-site bucket configured, `sudo felis offsite fetch-worlds` fetches them + instead (troubleshooting §16). +7. Delete `/var/lib/felis/retained/` once every server is back. + +## 4. Upgrading the pieces around Felis {#_4-upgrading-the-pieces-around-felis} + +A rerun of the installer upgrades Felis itself (§15). The components it installs keep +the version they were installed with unless noted: + +| Component | How a rerun treats it | Upgrade | +|---|---|---| +| Velocity, Limbo, Paper, LuckPerms | follow `deploy/game-stack.lock` | rerun after a release that moves the lock (§15b) | +| Temurin JRE | moves to the pinned patch build | rerun | +| k3s | left alone | rerun with `FELIS_UPGRADE_DEPS=1`: moves to the pinned release through that tag's install script, one minor version at a time (a bigger jump stops before anything changes and names the release to go through), never backwards | +| cloudflared | left alone | rerun with `FELIS_UPGRADE_DEPS=1`: swaps `/usr/local/bin/cloudflared` for the pinned, sha256-checked release and restarts `cloudflared-felis`; a cloudflared the distribution installed stays with its package manager | +| PostgreSQL | follows the image the release pins | a minor release comes with a Felis release, and the rerun restarts felis-postgres on it (a few seconds without the API); a major version is a dump and restore (below) | +| Docker, git, nftables | distribution packages | the package manager | + +```sh +curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap.sh \ + | sudo FELIS_UPGRADE_DEPS=1 bash +``` + +`sudo felis update` reports Felis, Velocity, k3s, cloudflared, the JRE and PostgreSQL +against their newest releases; `--k3s`, `--cloudflared`, `--jre` and `--postgres` narrow +it to one. PostgreSQL is read from the felis-postgres container and compared within its +major, since a minor release arrives with a Felis release, and a major past its end of life +gets a note naming the current one. + +The installer also sets up `felis-update-check.timer`, which runs `felis update --record` +once a day around 05:30 (and at boot after a missed run). `--record` stores the result +in `platform_settings`, and the panel's **Admin → Updates → Component versions** card +shows it: each component's installed and newest version, and for the ones with a newer +release the `sudo felis update --` line that prints how to apply it. Felis +applies nothing on its own; the installer re-run above is the apply path. The card turns +red when the newest record is older than 26 hours, meaning the timer stopped: + +```sh +systemctl list-timers felis-update-check.timer +journalctl -u felis-update-check -n 50 --no-pager +sudo felis update --record # record a fresh check now +``` + +### Bringing an older install up to date [VM-VERIFIED] {#bringing-an-older-install-up-to-date-vm-verified} + +Three pieces of an install keep the shape they had on the day they were created, and +neither `felis setup` nor `kubectl rollout restart` reaches them: the felis-api +Deployment (an env var added later, such as `FELIS_SMTP_PASSWORD`, is absent until the +Deployment is rendered again), the lobby image (built with whatever plugins the recipe +had then; LuckPerms came later, and without it every permission change from the panel +answers `luckperms_missing`), and the `MinecraftServer` specs (a field added later stays +unset). Bring all three forward in this order, images first: + +```sh +# 1. Rerun the installer: renders and applies the control-plane bundle, rebuilds and +# re-imports the login and lobby images, and recreates those two pods so they run +# the new images. The [smtp] relay the setup wizard wrote is carried forward. +curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap.sh | sudo bash + +# 2. Fill the spec fields the system servers gained since (troubleshooting §12b), then +# RCON for user servers created before it was the default. -user-rcon waits on +# each server's image opening RCON; see §12b before running it. +sudo felis converge +sudo felis converge -user-rcon +``` + +Check each piece: + +```sh +kubectl -n felis get deploy felis-api \ + -o jsonpath='{.spec.template.spec.containers[0].env[*].name}' | tr ' ' '\n' | grep SMTP +kubectl -n minecraft exec lobby-0 -- ls /data/plugins | grep -i luckperms +kubectl -n minecraft get minecraftserver \ + -o custom-columns=NAME:.metadata.name,RCON:.spec.rcon.enabled,IDLE:.spec.idle.autoStopEnabled +``` + +The env var only carries the password; mail still needs the relay itself, set in +`felis setup` → email. A user server picks up its new RCON block at its next start. + +### PostgreSQL major versions [CODE-ONLY] {#postgresql-major-versions-code-only} + +felis-postgres keeps its cluster in `/var/lib/felis/postgres//docker`. A release that +moves the image to a new major finds the old major's cluster there and stops before it +changes anything: the new server would start an empty cluster beside it. The way across is +a bundle, taken on the release you run now, restored into the new major's empty cluster: + +```sh +# On the release you run now: +b="$(sudo felis db backup -label pre-upgrade | sed -n 's/^felis db backup: wrote //p')" +sudo k3s kubectl -n felis scale deploy/felis-postgres --replicas=0 +sudo mv /var/lib/felis/postgres/18 /var/lib/felis/postgres-18.old # the old major's cluster, for a way back + +# Install the new release: it starts an empty cluster on the new major and creates the schema. +curl -fsSL /deploy/bootstrap.sh | sudo bash + +# Put the data back and bring its schema up to the new release. +sudo k3s kubectl -n felis scale deploy/felis-api deploy/felis-operator --replicas=0 +sudo felis db restore -yes -no-safety-backup "$b" +sudo felis migrate up -config /etc/felis/felis.host.toml +sudo k3s kubectl -n felis scale deploy/felis-api deploy/felis-operator --replicas=1 +``` + +Delete `/var/lib/felis/postgres-18.old` once the new release has run for a while. To go back +instead, scale felis-postgres to 0, move the new major's directory out of +`/var/lib/felis/postgres`, move `postgres-18.old` back as `/var/lib/felis/postgres/18`, and +rerun the older release's installer. + +### The database's move into k3s [VM-VERIFIED] [CI] {#the-database-s-move-into-k3s-vm-verified-ci} + +Releases before the move ran the database on a PostgreSQL the installer installed on the +host. The first rerun of a release with felis-postgres moves it, once: + +1. It stops felis-api, felis-operator and the host timers, and heads the host's + `pg_hba.conf` with a block that refuses every connection to `felis` but its own copy + (the original is kept beside it as `pg_hba.conf.pre-pg-move`). +2. It takes a `pre-pg-move` bundle of the host database (`felis db backup`), restores it + into felis-postgres (`felis db restore`) and compares the row count of every table on + both servers. Any failure up to here puts `pg_hba.conf` and the control plane back and + the platform keeps running on the host database, untouched. +3. It stops and disables the host `postgresql` service, which stays installed with its + copy of the data, and writes `/var/lib/felis/postgres-moved`. A host server that also + holds other databases keeps running; its `felis` copy is then reachable over loopback + only. + +The e2e upgrade job seeds the newest release's database with users, links, sessions, audit +rows, backups, builds and the rest (`deploy/e2e_seed.sh`), upgrades, and checks that +felis-postgres holds every seeded row with the same values after the pending migrations. +While that release is v0.1.0, the upgrade is this move. + +From then on the host config points at felis-postgres (`127.0.0.1:15432`, and +`deployment = "felis/felis-postgres"`, through which `felis db` runs `pg_dump`, `psql` +and `pg_restore` inside the pod) and the pods at `felis-postgres.felis.svc:5432`. + +To go back to the host database, for instance to reinstall the release before the move: + +```sh +sudo k3s kubectl -n felis scale deploy/felis-api deploy/felis-operator deploy/felis-postgres --replicas=0 +hba="$(sudo -u postgres psql -XtAc 'SHOW hba_file' 2>/dev/null || echo /var/lib/pgsql/data/pg_hba.conf)" +sudo cp -p "${hba}.pre-pg-move" "$hba" +sudo systemctl enable --now postgresql +sudo rm /var/lib/felis/postgres-moved +curl -fsSL /deploy/bootstrap.sh | sudo bash +``` + +`SHOW hba_file` needs the server running; with it stopped, the fallback path is EL's +(Debian and Ubuntu keep it in `/etc/postgresql//main/`). Whatever the platform wrote +after the move lives only in felis-postgres; take a bundle there first +(`sudo felis db backup`) and restore it onto the host database afterwards if that matters. +Once the move has run for a while, drop the host copy: +`sudo systemctl start postgresql; sudo -u postgres dropdb felis; sudo -u postgres dropuser felis`, +or remove the server package altogether. + +### The MinecraftServer CRD [VM-VERIFIED] {#the-minecraftserver-crd-vm-verified} + +Every rerun applies the CRD embedded in the `felis` binary (`felis bootstrap-assets crd`). +It serves and stores the single version `v1alpha1`, and the apiserver refuses values the +operator cannot act on: + +| Field | Accepted | +|---|---| +| `spec.rcon.port` | unset, `0` or `25575`: the allow-rcon NetworkPolicy opens only 25575, so any other port leaves the server unprobeable | +| `spec.startup.timeoutSeconds`, `readinessTimeoutSeconds` | 0 – 86400 | +| `spec.startup.healthHTTPPort` | 0 – 65535 | +| `spec.lifecycle.terminationGracePeriodSeconds` | 0 – 3600 | +| `spec.idle.emptySecondsBeforeStop` | 0 – 604800 (the panel caps it at 86400) | + +`0` means the operator's default throughout. An object stored before these rules keeps an +out-of-range value until someone edits that field (CRD validation ratcheting). The operator +reads a negative value as its default and an oversized one as written, so fix such a +value by hand: `kubectl -n minecraft edit minecraftserver `. + +**Moving to `v1beta1` (planned, not built).** The first breaking change to the spec ships as a new +version, in this order, each step one release: + +1. The CRD serves `v1alpha1` and `v1beta1`, storage stays `v1alpha1`. While the two + schemas carry the same fields, `conversion.strategy: None` suffices; a renamed or + reshaped field needs a conversion webhook, which felis-operator would serve. +2. Storage moves to `v1beta1`. The installer rewrites every object so etcd holds the new + version (`kubectl get minecraftservers -A -o json | kubectl replace -f -`), then sets + `status.storedVersions` of the CRD to `["v1beta1"]`. +3. A later release stops serving `v1alpha1`. Felis itself reads through one Go type at a + time, so the operator and felis-api switch in the release that moves storage. + +### Legacy-forwarded backends [VM-VERIFIED] {#legacy-forwarded-backends-vm-verified} + +A 1.8-era backend sits behind ViaVersion, which drops modern forwarding's login plugin +message on the way down to protocol 47, so the proxy has to hand that server the +player's identity BungeeCord-style, in the handshake address. Only the Felis-Legacy +Velocity fork can do that per server. Mark the server's CR and the proxy picks it up at +its next server-list refresh (every 15 s): + +```sh +kubectl -n minecraft label minecraftserver felis.lolicon.best/forwarding=legacy +kubectl -n minecraft label minecraftserver felis.lolicon.best/forwarding- # back to modern +journalctl -u felis-velocity | grep 'legacy forwarding list' +``` + +The installer's `FELIS_LEGACY_FORWARDING_SERVERS` (default `legacy18`) stays in the list +whatever the labels say. What a label does depends on the proxy the host runs: + +| Proxy | A label applies | +|---|---| +| Fork with patch 0004 (`build-velocity.sh` default arm) | from the next connection to that server | +| Fork with 0003 alone (`--deployed`) | at the next `systemctl restart felis-velocity` | +| Stock Velocity | never; the log line is a warning naming the server | + +On the test VM (fork with 0004) labelling a server logged `legacy forwarding list is now +[legacy18,resolvecheck]` 12 s later, and removing the label logged the list back to +`[legacy18]`. The fork's own test (`FelisLegacyForwardingTest`) covers the next +connection following the rewritten list. + +Legacy forwarding carries no secret. A marked server believes any identity that reaches +its game port, which `felis-allow-game-from-velocity` limits to the proxy and the node +itself; anything else running on the node can reach it too. + +## 5. Disaster recovery {#_5-disaster-recovery} + +The procedures are in §16: what a database bundle holds, restoring one on the same host, +rolling back an upgrade, and rebuilding on a new host from the off-site copy. For a +production install: + +- **Configure the off-site copy** (`FELIS_OFFSITE_*`, §16 "Keep a copy somewhere + else"). Without it the world archives sit on the same disk as the worlds, and the + database bundles on the same disk as the database; losing the disk loses both. The + installer ends with `NO OFF-SITE COPY` until it is set. +- **Keep the off-site encryption key off the host**, in a password manager. The bucket + holds only sealed objects. +- **Keep one database bundle off the host** as well when there is no bucket. It contains + `secrets.env`, which a rebuild needs to read the rest. +- **Rehearse the rebuild** once on a spare VM: troubleshooting.md §16 "Rebuild on a new + host", every step but 8 (take-over) and 11 (the tunnel), then its checks: sign in with + an email code, restore one world and join it. `felis offsite status` and `felis db check` exit + non-zero when the copy or the newest daily bundle is stale; wire them into your monitoring, + or rely on the watchdog's mail. + +### Moving to another host (planned) {#moving-to-another-host-planned} + +A planned move is the rebuild of troubleshooting.md §16, with the old host still there to +hand over a copy that misses nothing. It needs the off-site bucket: that is how the world +archives reach the new host (§16 step 7). The platform is down from step 1 until the new +host serves. + +1. **On the old host**, stop everything that changes a world, then send the last copy: + + ```bash + sudo install -d -m 0755 /run/felis + echo $(( $(date +%s) + 4 * 3600 )) | sudo tee /run/felis/watchdog-quiet-until + sudo systemctl stop felis-velocity # no joins, so no server wakes + sudo felis backup-now -yes -stop # every world archived; the servers stay stopped + sudo k3s kubectl -n felis scale deploy/felis-operator --replicas=0 # nothing starts a server from here on + sudo felis db backup # a bundle that lists those archives + sudo systemctl start felis-offsite.service + sudo felis offsite status # again until nothing waits + ``` + + The order matters. The new host fetches the archives its restored database lists, so + the bundle comes after the last archive. The operator goes after `backup-now`, which + needs it to stop the servers. The quiet marker keeps the watchdog from mailing the + owners about the stopped proxy and operator for the next 4 hours. +2. **On the new host**, follow troubleshooting.md §16 "Rebuild on a new host" from step 1; + `fetch-db latest` picks the bundle the old host just sent. Step 8 (`felis offsite + take-over`) makes the new host the one that writes the bucket, and from then on the + old host copies nothing more. Steps 10 and 11 move the names and the tunnel. +3. **Check the new host** before announcing it: sign in with an email code, restore one + world and join it, and see `sudo felis offsite status` show a recent `last success` and + no stand-by notice. +4. **Retire the old host.** It holds the last copy of every world outside the bucket, so + keep it powered off with its disk for a few days first, disabled so a boot brings + nothing up: + + ```bash + sudo systemctl disable k3s felis-velocity felis-watchdog.timer felis-offsite.timer \ + felis-db-backup.timer felis-update-check.timer felis-build-tools.timer + sudo poweroff + ``` + + Then uninstall it (§3) or wipe it. + +Each step is covered where it is documented (backup-now in troubleshooting.md §10, the +rebuild in §16); the sequence as a whole has not been rehearsed as one move. + +## 6. Changing the root domain [VM-VERIFIED] [GO-TESTED] [SH-TESTED] {#_6-changing-the-root-domain-vm-verified-go-tested-sh-tested} + +The root domain is written into more places than the installer's config: the panel +certificate (`/etc/felis/panel-tls.crt`), the `felis-config` Secret in both namespaces, +the `felis-api-tls` Secret, the proxy's `felis-link.properties`, the login gate's +`MinecraftServer` env (`FELIS_ROOT_DOMAIN`, `FELIS_PANEL_HOSTNAME`), the Cloudflare tunnel +and DNS. `felis domain set` moves every one of them that lives on the host, in that +order, then restarts what reads them; `felis domain check` reports each surface on its +own line. The installer keeps the installed domain: a rerun with a different +`FELIS_ROOT_DOMAIN` stops and names this command. + +```sh +sudo felis domain set new.example.net # the plan: every surface, what it moves to, what it costs +sudo felis domain set -yes new.example.net # do it +sudo felis domain check # one line per surface; exits 1 while any is behind +``` + +What it keeps: + +- A panel or admin-console hostname set by hand in `[auth]` (anything other than + `console.` / `op.console.`) stays as it is; change it in + `/etc/felis/felis.host.toml` yourself if it should move, then run `set` again. +- The other `[auth]` keys (`access_jwt_aud`, `client_ip_header`) and every other line of + both config files. The edit refuses a file it cannot change line for line (a multi-line + value, a quoted or dotted key) and names what to fix. +- An operator's own certificate. The installer's self-signed certificate is reissued for + the new names (same shape, the old pair saved beside it as `*.pre-domain-