refactor: 为翻译做准备
1
.github/workflows/deploy.yml
vendored
@@ -24,6 +24,7 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -71,30 +71,30 @@ export const config = defineConfig({
|
||||
|
||||
function nav(): DefaultTheme.NavItem[] {
|
||||
return [
|
||||
{text: '首页', link: '/'},
|
||||
{text: '文档', link: '/quickstart/install'},
|
||||
{text: '支持', link: '/support'},
|
||||
{text: '🔥证书', link: '/cert'},
|
||||
{text: '关于', link: '/about'},
|
||||
{text: 'Home', link: '/'},
|
||||
{text: 'Document', link: '/quickstart/install'},
|
||||
{text: 'Support', link: '/support'},
|
||||
{text: '🔥Certificate', link: '/cert'},
|
||||
{text: 'About', link: '/about'},
|
||||
]
|
||||
}
|
||||
|
||||
function sidebarQuickstart(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{
|
||||
text: '安装面板',
|
||||
text: 'Installing panel',
|
||||
link: '/install'
|
||||
},
|
||||
{
|
||||
text: '挂载分区',
|
||||
text: 'Mounting partition',
|
||||
link: '/disk'
|
||||
},
|
||||
{
|
||||
text: '管理面板',
|
||||
text: 'Managing panel',
|
||||
link: '/panel'
|
||||
},
|
||||
{
|
||||
text: '管理容器',
|
||||
text: 'Managing container',
|
||||
link: '/container'
|
||||
},
|
||||
]
|
||||
@@ -103,31 +103,31 @@ function sidebarQuickstart(): DefaultTheme.SidebarItem[] {
|
||||
function sidebarAdvanced(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{
|
||||
text: '面板 API',
|
||||
text: 'Panel API',
|
||||
link: '/api'
|
||||
},
|
||||
{
|
||||
text: '配置容器镜像加速',
|
||||
text: 'Configure container image acceleration',
|
||||
link: '/hub-mirror'
|
||||
},
|
||||
{
|
||||
text: '配置反向代理',
|
||||
text: 'Configure reverse proxy',
|
||||
link: '/proxy'
|
||||
},
|
||||
{
|
||||
text: '配置进程守护',
|
||||
text: 'Configure process daemon',
|
||||
link: '/supervisor'
|
||||
},
|
||||
{
|
||||
text: '配置 QUIC(HTTP3)',
|
||||
text: 'Configure QUIC (HTTP3)',
|
||||
link: '/quic'
|
||||
},
|
||||
{
|
||||
text: '配置 TLSv1.1 TLSv1',
|
||||
text: 'Configure TLSv1.1 TLSv1',
|
||||
link: '/tls'
|
||||
},
|
||||
{
|
||||
text: '安全性建议',
|
||||
text: 'Security recommendations',
|
||||
link: '/safe'
|
||||
},
|
||||
]
|
||||
|
||||
118
en/about.md
@@ -10,8 +10,8 @@ import { VPTeamMembers } from 'vitepress/theme'
|
||||
const members = [
|
||||
{
|
||||
avatar: 'https://weavatar.com/avatar/18e77debb1bc0000c0b50757b8f1bebb2c3e4df3d494124f776c15dbc1ebe8a5',
|
||||
name: '耗子',
|
||||
desc: '创始人 & CEO',
|
||||
name: 'Rat',
|
||||
desc: 'Founder & CEO',
|
||||
links: [
|
||||
{ icon: 'github', link: 'https://github.com/devhaozi' },
|
||||
{ icon: 'bilibili', link: 'https://space.bilibili.com/8067' }
|
||||
@@ -19,8 +19,8 @@ const members = [
|
||||
},
|
||||
{
|
||||
avatar: 'https://weavatar.com/avatar/f6b23deadaa481f0b3ea75ad94f246881ed2326117efebad6f2799ea165779b9',
|
||||
name: '靓仔',
|
||||
desc: '技术负责人',
|
||||
name: 'Liang Zai',
|
||||
desc: 'Technical Director',
|
||||
links: [
|
||||
{ icon: 'github', link: 'https://github.com/205125' }
|
||||
]
|
||||
@@ -28,100 +28,40 @@ const members = [
|
||||
]
|
||||
</script>
|
||||
|
||||
# 关于
|
||||
# About
|
||||
|
||||
## 关于耗子面板
|
||||
## About RatPanel
|
||||
|
||||
耗子面板是一款专业的服务器运维管理面板,致力于为用户提供简单、高效、安全的服务器管理解决方案。
|
||||
RatPanel is a professional server operation and maintenance management panel dedicated to providing users with simple,
|
||||
efficient, and secure server management solutions.
|
||||
|
||||
| 愿景 | 使命 | 价值观 |
|
||||
|-------------------|---------------|----------------|
|
||||
| 成为领先的服务器管理解决方案提供商 | 让服务器管理变得简单而高效 | 用户至上、创新驱动、专业专注 |
|
||||
| Vision | Mission | Values |
|
||||
|----------------------------------------------------------|---------------------------------------------|---------------------------------------------------|
|
||||
| Become a leading provider of server management solutions | Make server management simple and efficient | User-first, Innovation-driven, Professional focus |
|
||||
|
||||
## 团队介绍
|
||||
## Team Introduction
|
||||
|
||||
<VPTeamMembers size="small" :members="members" />
|
||||
|
||||
## 发展历程
|
||||
## Development History
|
||||
|
||||
<style>
|
||||
.about-timeline {
|
||||
max-width: 800px;
|
||||
margin: 0 auto 60px;
|
||||
position: relative;
|
||||
}
|
||||
.about-timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
background: #e7f1ff;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
}
|
||||
.about-timeline-item {
|
||||
margin-bottom: 30px;
|
||||
position: relative;
|
||||
}
|
||||
.about-timeline-content {
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 45%;
|
||||
position: relative;
|
||||
}
|
||||
.about-timeline-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #4a90e2;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.about-year {
|
||||
color: #4a90e2;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.about-h3 {
|
||||
margin: 0!important;
|
||||
}
|
||||
</style>
|
||||
::: timeline 2024 - New Journey
|
||||
The Panel received support from numerous sponsors, and released the brand new version 2.3 in the second half of 2024
|
||||
:::
|
||||
|
||||
<div class="about-timeline">
|
||||
<div class="about-timeline-item">
|
||||
<div class="about-timeline-content" style="margin-left: auto;">
|
||||
<div class="about-year">2024</div>
|
||||
<h3 class="about-h3">全新起航</h3>
|
||||
<p>面板得到了众多赞助商的支持,2024 年下半年发布了全新的 2.3 版本</p>
|
||||
</div>
|
||||
<div class="about-timeline-dot"></div>
|
||||
</div>
|
||||
::: timeline 2023 - Technical Accumulation
|
||||
Rewrote the Panel using Go, released versions 2.0 and 2.1 series, accumulated extensive development experience
|
||||
:::
|
||||
|
||||
<div class="about-timeline-item">
|
||||
<div class="about-timeline-content">
|
||||
<div class="about-year">2023</div>
|
||||
<h3 class="about-h3">技术积累</h3>
|
||||
<p>使用 Go 对面板进行重写,发布 2.0 2.1 系列版本,积累了大量开发经验</p>
|
||||
</div>
|
||||
<div class="about-timeline-dot"></div>
|
||||
</div>
|
||||
::: timeline 2022 - Project Initiation
|
||||
Project initiated in mid-2022, version 1.0 released at the end of the year
|
||||
:::
|
||||
|
||||
<div class="about-timeline-item">
|
||||
<div class="about-timeline-content" style="margin-left: auto;">
|
||||
<div class="about-year">2022</div>
|
||||
<h3 class="about-h3">项目立项</h3>
|
||||
<p>2022 年中项目立项,年末发布 1.0 版本</p>
|
||||
</div>
|
||||
<div class="about-timeline-dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
## Contact Us
|
||||
|
||||
## 联系我们
|
||||
|
||||
| 名称 | 联系方式 |
|
||||
|------|--------------------------------------------------------------------------|
|
||||
| 企业微信 | [点击联系](https://work.weixin.qq.com/kfid/kfc20ea8e38b5a4e73a) |
|
||||
| QQ | [826896000](https://wpa.qq.com/msgrd?v=3&uin=826896000&site=qq&menu=yes) |
|
||||
| 电子邮件 | [admin@haozi.net](mailto:admin@haozi.net) |
|
||||
| 公司地址 | 天津市武清区黄庄街道泉里路1号智库大厦206室 |
|
||||
| Name | Contact |
|
||||
|-----------------|------------------------------------------------------------------------------------------|
|
||||
| WeChat Work | [Click to Contact](https://work.weixin.qq.com/kfid/kfc20ea8e38b5a4e73a) |
|
||||
| QQ | [826896000](https://wpa.qq.com/msgrd?v=3&uin=826896000&site=qq&menu=yes) |
|
||||
| Email | [admin@haozi.net](mailto:admin@haozi.net) |
|
||||
| Company Address | Room 206, Zhiku Building, No.1 Quanli Road, Huangzhuang Street, Wuqing District, Tianjin |
|
||||
|
||||
@@ -1,65 +1,67 @@
|
||||
# API 参考文档
|
||||
# API Reference Documentation
|
||||
|
||||
## 概述
|
||||
## Overview
|
||||
|
||||
耗子面板提供了一套安全的 RESTful 接口,用于与面板系统进行交互。所有 API 请求都需要进行 HMAC-SHA256 签名认证以确保通信的安全性和完整性。
|
||||
RatPanel provides a secure RESTful interface for interacting with the panel system. All API requests require HMAC-SHA256
|
||||
signature authentication to ensure the security and integrity of communications.
|
||||
|
||||
## 基础信息
|
||||
## Basic Information
|
||||
|
||||
- **基础 URL**: `http(s)://your-panel-domain/{entry}/api/`
|
||||
- **内容类型**: 所有请求和响应均使用 `application/json`
|
||||
- **字符编码**: UTF-8
|
||||
- **Base URL**: `http(s)://your-panel-domain/{entry}/api/`
|
||||
- **Content Type**: All requests and responses use `application/json`
|
||||
- **Character Encoding**: UTF-8
|
||||
|
||||
## 认证机制
|
||||
## Authentication Mechanism
|
||||
|
||||
API 使用 HMAC-SHA256 签名算法进行认证。每个请求必须包含以下 HTTP 头:
|
||||
The API uses the HMAC-SHA256 signature algorithm for authentication. Each request must include the following HTTP
|
||||
headers:
|
||||
|
||||
| 头部名称 | 描述 |
|
||||
|-----------------|-----------------------------------------------------------------|
|
||||
| `Content-Type` | 设置为 `application/json` |
|
||||
| `X-Timestamp` | 当前 UNIX 时间戳(秒) |
|
||||
| `Authorization` | 身份验证信息,格式为 `HMAC-SHA256 Credential={id}, Signature={signature}` |
|
||||
| Header Name | Description |
|
||||
|-----------------|------------------------------------------------------------------------------------------|
|
||||
| `Content-Type` | Set to `application/json` |
|
||||
| `X-Timestamp` | Current UNIX timestamp (seconds) |
|
||||
| `Authorization` | Authentication information, format: `HMAC-SHA256 Credential={id}, Signature={signature}` |
|
||||
|
||||
## 签名算法
|
||||
## Signature Algorithm
|
||||
|
||||
签名过程包含四个主要步骤:
|
||||
The signature process consists of four main steps:
|
||||
|
||||
### 1. 构造规范化请求
|
||||
### 1. Construct Canonical Request
|
||||
|
||||
规范化请求字符串由以下部分组成,各部分之间使用换行符(\n)分隔:
|
||||
The canonical request string consists of the following parts, separated by newline characters (\n):
|
||||
|
||||
```
|
||||
HTTP方法
|
||||
规范化路径
|
||||
规范化查询字符串
|
||||
请求体的SHA256哈希值
|
||||
HTTP Method
|
||||
Canonical Path
|
||||
Canonical Query String
|
||||
SHA256 Hash of Request Body
|
||||
```
|
||||
|
||||
**注意**:规范化路径应始终使用 `/api/` 开头的路径部分,忽略入口前缀。
|
||||
**Note**: The canonical path should always use the path part starting with `/api/`, ignoring the entry prefix.
|
||||
|
||||
### 2. 构造待签名字符串
|
||||
### 2. Construct String to Sign
|
||||
|
||||
待签名字符串包含以下部分,各部分使用换行符(\n)分隔:
|
||||
The string to sign consists of the following parts, separated by newline characters (\n):
|
||||
|
||||
```
|
||||
"HMAC-SHA256"
|
||||
时间戳
|
||||
规范化请求的SHA256哈希值
|
||||
Timestamp
|
||||
SHA256 Hash of Canonical Request
|
||||
```
|
||||
|
||||
### 3. 计算签名
|
||||
### 3. Calculate Signature
|
||||
|
||||
使用您的令牌(token)对待签名字符串进行 HMAC-SHA256 计算,然后将结果转换为十六进制字符串。
|
||||
Calculate HMAC-SHA256 on the string to sign using your token, then convert the result to a hexadecimal string.
|
||||
|
||||
### 4. 构造授权头
|
||||
### 4. Construct Authorization Header
|
||||
|
||||
将计算得到的签名添加到 `Authorization` 头:
|
||||
Add the calculated signature to the `Authorization` header:
|
||||
|
||||
```
|
||||
Authorization: HMAC-SHA256 Credential={id}, Signature={signature}
|
||||
```
|
||||
|
||||
## Go 示例
|
||||
## Go Example
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -77,23 +79,23 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建一个获取用户信息的请求
|
||||
// Create a request to get user information
|
||||
req, err := http.NewRequest("GET", "http://example.com/entrance/api/user/info", nil)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating request:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置内容类型
|
||||
// Set content type
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 签名请求 - 传入您的用户ID和API令牌
|
||||
// Sign request - pass your user ID and API token
|
||||
if err = SignReq(req, uint(16), "YourSecretToken"); err != nil {
|
||||
fmt.Println("Error signing request:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
// Send request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -102,7 +104,7 @@ func main() {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 处理响应
|
||||
// Handle response
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading response:", err)
|
||||
@@ -113,23 +115,23 @@ func main() {
|
||||
fmt.Println("Response Body:", string(body))
|
||||
}
|
||||
|
||||
// SignReq 对HTTP请求进行签名
|
||||
// SignReq signs an HTTP request
|
||||
func SignReq(req *http.Request, id uint, token string) error {
|
||||
// 步骤一:构造规范化请求
|
||||
// Step 1: Construct canonical request
|
||||
var body []byte
|
||||
var err error
|
||||
|
||||
if req.Body != nil {
|
||||
// 读取并保存请求体
|
||||
// Read and save request body
|
||||
body, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 恢复请求体以便后续使用
|
||||
// Restore request body for subsequent use
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
}
|
||||
|
||||
// 规范化路径
|
||||
// Canonical path
|
||||
canonicalPath := req.URL.Path
|
||||
if !strings.HasPrefix(canonicalPath, "/api") {
|
||||
index := strings.Index(canonicalPath, "/api")
|
||||
@@ -144,7 +146,7 @@ func SignReq(req *http.Request, id uint, token string) error {
|
||||
req.URL.Query().Encode(),
|
||||
SHA256(string(body)))
|
||||
|
||||
// 步骤二:设置时间戳和构造待签名字符串
|
||||
// Step 2: Set timestamp and construct string to sign
|
||||
timestamp := time.Now().Unix()
|
||||
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", timestamp))
|
||||
|
||||
@@ -153,10 +155,10 @@ func SignReq(req *http.Request, id uint, token string) error {
|
||||
timestamp,
|
||||
SHA256(canonicalRequest))
|
||||
|
||||
// 步骤三:计算签名
|
||||
// Step 3: Calculate signature
|
||||
signature := HMACSHA256(stringToSign, token)
|
||||
|
||||
// 步骤四:设置Authorization头
|
||||
// Step 4: Set Authorization header
|
||||
authHeader := fmt.Sprintf("HMAC-SHA256 Credential=%d, Signature=%s", id, signature)
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
|
||||
@@ -177,21 +179,21 @@ func HMACSHA256(data string, secret string) string {
|
||||
}
|
||||
```
|
||||
|
||||
## PHP 示例
|
||||
## PHP Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* 耗子面板 API 请求示例 (PHP)
|
||||
* RatPanel API Request Example (PHP)
|
||||
*/
|
||||
|
||||
function signRequest($method, $url, $body, $id, $token) {
|
||||
// 解析URL并获取路径
|
||||
// Parse URL and get path
|
||||
$parsedUrl = parse_url($url);
|
||||
$path = $parsedUrl['path'];
|
||||
$query = isset($parsedUrl['query']) ? $parsedUrl['query'] : '';
|
||||
|
||||
// 规范化路径
|
||||
// Canonical path
|
||||
$canonicalPath = $path;
|
||||
if (strpos($path, '/api') !== 0) {
|
||||
$apiPos = strpos($path, '/api');
|
||||
@@ -200,10 +202,10 @@ function signRequest($method, $url, $body, $id, $token) {
|
||||
}
|
||||
}
|
||||
|
||||
// 计算请求体的SHA256哈希值
|
||||
// Calculate SHA256 hash of request body
|
||||
$bodySha256 = hash('sha256', $body ?: '');
|
||||
|
||||
// 构造规范化请求
|
||||
// Construct canonical request
|
||||
$canonicalRequest = implode("\n", [
|
||||
$method,
|
||||
$canonicalPath,
|
||||
@@ -211,20 +213,20 @@ function signRequest($method, $url, $body, $id, $token) {
|
||||
$bodySha256
|
||||
]);
|
||||
|
||||
// 获取当前时间戳
|
||||
// Get current timestamp
|
||||
$timestamp = time();
|
||||
|
||||
// 构造待签名字符串
|
||||
// Construct string to sign
|
||||
$stringToSign = implode("\n", [
|
||||
'HMAC-SHA256',
|
||||
$timestamp,
|
||||
hash('sha256', $canonicalRequest)
|
||||
]);
|
||||
|
||||
// 计算签名
|
||||
// Calculate signature
|
||||
$signature = hash_hmac('sha256', $stringToSign, $token);
|
||||
|
||||
// 返回签名和时间戳
|
||||
// Return signature and timestamp
|
||||
return [
|
||||
'timestamp' => $timestamp,
|
||||
'signature' => $signature,
|
||||
@@ -232,24 +234,24 @@ function signRequest($method, $url, $body, $id, $token) {
|
||||
];
|
||||
}
|
||||
|
||||
// 示例请求
|
||||
// Example request
|
||||
$apiUrl = 'http://example.com/entrance/api/user/info';
|
||||
$method = 'GET';
|
||||
$body = ''; // 对于GET请求,通常没有请求体
|
||||
$body = ''; // For GET requests, usually no request body
|
||||
$id = 16;
|
||||
$token = 'YourSecretToken';
|
||||
|
||||
// 生成签名信息
|
||||
// Generate signature information
|
||||
$signingData = signRequest($method, $apiUrl, $body, $id, $token);
|
||||
|
||||
// 准备HTTP请求头
|
||||
// Prepare HTTP headers
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'X-Timestamp: ' . $signingData['timestamp'],
|
||||
'Authorization: HMAC-SHA256 Credential=' . $signingData['id'] . ', Signature=' . $signingData['signature']
|
||||
];
|
||||
|
||||
// 使用cURL发送请求
|
||||
// Use cURL to send request
|
||||
$ch = curl_init($apiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
@@ -259,17 +261,17 @@ if (!empty($body)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
}
|
||||
|
||||
// 执行请求并获取响应
|
||||
// Execute request and get response
|
||||
$response = curl_exec($ch);
|
||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// 输出结果
|
||||
echo "响应状态码: " . $statusCode . PHP_EOL;
|
||||
echo "响应内容: " . $response . PHP_EOL;
|
||||
// Output results
|
||||
echo "Response Status Code: " . $statusCode . PHP_EOL;
|
||||
echo "Response Content: " . $response . PHP_EOL;
|
||||
```
|
||||
|
||||
## Python 示例
|
||||
## Python Example
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
@@ -280,28 +282,28 @@ import time
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
def sha256_hash(text):
|
||||
"""计算字符串的SHA256哈希值"""
|
||||
"""Calculate SHA256 hash of a string"""
|
||||
return hashlib.sha256(text.encode('utf-8')).hexdigest()
|
||||
|
||||
def hmac_sha256(key, message):
|
||||
"""使用HMAC-SHA256算法计算签名"""
|
||||
"""Calculate signature using HMAC-SHA256 algorithm"""
|
||||
return hmac.new(key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()
|
||||
|
||||
def sign_request(method, url, body, user_id, token):
|
||||
"""为API请求生成签名"""
|
||||
# 解析URL
|
||||
"""Generate signature for API request"""
|
||||
# Parse URL
|
||||
parsed_url = urlparse(url)
|
||||
path = parsed_url.path
|
||||
query = parsed_url.query
|
||||
|
||||
# 规范化路径
|
||||
# Canonical path
|
||||
canonical_path = path
|
||||
if not path.startswith('/api'):
|
||||
api_pos = path.find('/api')
|
||||
if api_pos != -1:
|
||||
canonical_path = path[api_pos:]
|
||||
|
||||
# 构造规范化请求
|
||||
# Construct canonical request
|
||||
body_str = body if body else ""
|
||||
canonical_request = "\n".join([
|
||||
method,
|
||||
@@ -310,17 +312,17 @@ def sign_request(method, url, body, user_id, token):
|
||||
sha256_hash(body_str)
|
||||
])
|
||||
|
||||
# 获取当前时间戳
|
||||
# Get current timestamp
|
||||
timestamp = int(time.time())
|
||||
|
||||
# 构造待签名字符串
|
||||
# Construct string to sign
|
||||
string_to_sign = "\n".join([
|
||||
"HMAC-SHA256",
|
||||
str(timestamp),
|
||||
sha256_hash(canonical_request)
|
||||
])
|
||||
|
||||
# 计算签名
|
||||
# Calculate signature
|
||||
signature = hmac_sha256(token, string_to_sign)
|
||||
|
||||
return {
|
||||
@@ -329,24 +331,24 @@ def sign_request(method, url, body, user_id, token):
|
||||
"id": user_id
|
||||
}
|
||||
|
||||
# 示例请求
|
||||
# Example request
|
||||
api_url = "http://example.com/entrance/api/user/info"
|
||||
method = "GET"
|
||||
body = "" # GET请求通常没有请求体
|
||||
body = "" # GET requests typically have no body
|
||||
user_id = 16
|
||||
token = "YourSecretToken"
|
||||
|
||||
# 生成签名信息
|
||||
# Generate signature information
|
||||
signing_data = sign_request(method, api_url, body, user_id, token)
|
||||
|
||||
# 准备HTTP请求头
|
||||
# Prepare HTTP headers
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Timestamp": str(signing_data["timestamp"]),
|
||||
"Authorization": f"HMAC-SHA256 Credential={signing_data['id']}, Signature={signing_data['signature']}"
|
||||
}
|
||||
|
||||
# 发送请求
|
||||
# Send request
|
||||
response = requests.request(
|
||||
method=method,
|
||||
url=api_url,
|
||||
@@ -354,12 +356,12 @@ response = requests.request(
|
||||
data=body
|
||||
)
|
||||
|
||||
# 输出结果
|
||||
print(f"响应状态码: {response.status_code}")
|
||||
print(f"响应内容: {response.text}")
|
||||
# Output results
|
||||
print(f"Response Status Code: {response.status_code}")
|
||||
print(f"Response Content: {response.text}")
|
||||
```
|
||||
|
||||
## Java 示例
|
||||
## Java Example
|
||||
|
||||
```java
|
||||
import java.net.URI;
|
||||
@@ -374,23 +376,23 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 耗子面板 API 请求示例 (Java)
|
||||
* RatPanel API Request Example (Java)
|
||||
*/
|
||||
public class RatPanelApiExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 示例请求
|
||||
// Example request
|
||||
String apiUrl = "http://example.com/entrance/api/user/info";
|
||||
String method = "GET";
|
||||
String body = ""; // 对于GET请求,通常没有请求体
|
||||
String body = ""; // For GET requests, usually no request body
|
||||
int id = 16;
|
||||
String token = "YourSecretToken";
|
||||
|
||||
// 生成签名信息
|
||||
// Generate signature information
|
||||
SigningData signingData = signRequest(method, apiUrl, body, id, token);
|
||||
|
||||
// 准备HTTP请求
|
||||
// Prepare HTTP request
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(apiUrl))
|
||||
@@ -399,7 +401,7 @@ public class RatPanelApiExample {
|
||||
.header("Authorization", "HMAC-SHA256 Credential=" + signingData.id +
|
||||
", Signature=" + signingData.signature);
|
||||
|
||||
// 设置请求方法和请求体
|
||||
// Set request method and body
|
||||
if (method.equals("GET")) {
|
||||
requestBuilder.GET();
|
||||
} else {
|
||||
@@ -408,12 +410,12 @@ public class RatPanelApiExample {
|
||||
|
||||
HttpRequest request = requestBuilder.build();
|
||||
|
||||
// 发送请求
|
||||
// Send request
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// 输出结果
|
||||
System.out.println("响应状态码: " + response.statusCode());
|
||||
System.out.println("响应内容: " + response.body());
|
||||
// Output results
|
||||
System.out.println("Response Status Code: " + response.statusCode());
|
||||
System.out.println("Response Content: " + response.body());
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -433,12 +435,12 @@ public class RatPanelApiExample {
|
||||
}
|
||||
|
||||
public static SigningData signRequest(String method, String url, String body, int id, String token) throws Exception {
|
||||
// 解析URL
|
||||
// Parse URL
|
||||
URI uri = new URI(url);
|
||||
String path = uri.getPath();
|
||||
String query = uri.getQuery() != null ? uri.getQuery() : "";
|
||||
|
||||
// 规范化路径
|
||||
// Canonical path
|
||||
String canonicalPath = path;
|
||||
if (!path.startsWith("/api")) {
|
||||
int apiPos = path.indexOf("/api");
|
||||
@@ -447,29 +449,29 @@ public class RatPanelApiExample {
|
||||
}
|
||||
}
|
||||
|
||||
// 计算请求体的SHA256哈希值
|
||||
// Calculate SHA256 hash of request body
|
||||
String bodySha256 = sha256Hash(body != null ? body : "");
|
||||
|
||||
// 构造规范化请求
|
||||
// Construct canonical request
|
||||
String canonicalRequest = String.join("\n",
|
||||
method,
|
||||
canonicalPath,
|
||||
query,
|
||||
bodySha256);
|
||||
|
||||
// 获取当前时间戳
|
||||
// Get current timestamp
|
||||
long timestamp = Instant.now().getEpochSecond();
|
||||
|
||||
// 构造待签名字符串
|
||||
// Construct string to sign
|
||||
String stringToSign = String.join("\n",
|
||||
"HMAC-SHA256",
|
||||
String.valueOf(timestamp),
|
||||
sha256Hash(canonicalRequest));
|
||||
|
||||
// 计算签名
|
||||
// Calculate signature
|
||||
String signature = hmacSha256(token, stringToSign);
|
||||
|
||||
// 返回签名和时间戳
|
||||
// Return signature and timestamp
|
||||
return new SigningData(timestamp, signature, id);
|
||||
}
|
||||
|
||||
@@ -501,7 +503,7 @@ public class RatPanelApiExample {
|
||||
}
|
||||
```
|
||||
|
||||
## Node.js 示例
|
||||
## Node.js Example
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
@@ -509,153 +511,154 @@ const axios = require('axios');
|
||||
const url = require('url');
|
||||
|
||||
/**
|
||||
* 计算字符串的SHA256哈希值
|
||||
* @param {string} text 待哈希的字符串
|
||||
* @returns {string} 哈希结果(十六进制)
|
||||
* Calculate SHA256 hash of a string
|
||||
* @param {string} text The string to hash
|
||||
* @returns {string} Hash result (hexadecimal)
|
||||
*/
|
||||
function sha256Hash(text) {
|
||||
return crypto.createHash('sha256').update(text || '').digest('hex');
|
||||
return crypto.createHash('sha256').update(text || '').digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用HMAC-SHA256算法计算签名
|
||||
* @param {string} key 密钥
|
||||
* @param {string} message 待签名的消息
|
||||
* @returns {string} 签名结果(十六进制)
|
||||
* Calculate signature using HMAC-SHA256 algorithm
|
||||
* @param {string} key The key
|
||||
* @param {string} message The message to sign
|
||||
* @returns {string} Signature result (hexadecimal)
|
||||
*/
|
||||
function hmacSha256(key, message) {
|
||||
return crypto.createHmac('sha256', key).update(message).digest('hex');
|
||||
return crypto.createHmac('sha256', key).update(message).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* 为API请求生成签名
|
||||
* @param {string} method HTTP方法
|
||||
* @param {string} apiUrl API地址
|
||||
* @param {string} body 请求体
|
||||
* @param {number} id 用户ID
|
||||
* @param {string} token 密钥
|
||||
* @returns {object} 包含签名、时间戳和ID的对象
|
||||
* Generate signature for API request
|
||||
* @param {string} method HTTP method
|
||||
* @param {string} apiUrl API URL
|
||||
* @param {string} body Request body
|
||||
* @param {number} id User ID
|
||||
* @param {string} token Secret key
|
||||
* @returns {object} Object containing signature, timestamp and ID
|
||||
*/
|
||||
function signRequest(method, apiUrl, body, id, token) {
|
||||
// 解析URL
|
||||
const parsedUrl = new url.URL(apiUrl);
|
||||
const path = parsedUrl.pathname;
|
||||
const query = parsedUrl.search.slice(1); // 移除开头的'?'
|
||||
|
||||
// 规范化路径
|
||||
let canonicalPath = path;
|
||||
if (!path.startsWith('/api')) {
|
||||
const apiPos = path.indexOf('/api');
|
||||
if (apiPos !== -1) {
|
||||
canonicalPath = path.slice(apiPos);
|
||||
// Parse URL
|
||||
const parsedUrl = new url.URL(apiUrl);
|
||||
const path = parsedUrl.pathname;
|
||||
const query = parsedUrl.search.slice(1); // Remove leading '?'
|
||||
|
||||
// Canonical path
|
||||
let canonicalPath = path;
|
||||
if (!path.startsWith('/api')) {
|
||||
const apiPos = path.indexOf('/api');
|
||||
if (apiPos !== -1) {
|
||||
canonicalPath = path.slice(apiPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构造规范化请求
|
||||
const canonicalRequest = [
|
||||
method,
|
||||
canonicalPath,
|
||||
query,
|
||||
sha256Hash(body || '')
|
||||
].join('\n');
|
||||
|
||||
// 获取当前时间戳
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
// 构造待签名字符串
|
||||
const stringToSign = [
|
||||
'HMAC-SHA256',
|
||||
timestamp,
|
||||
sha256Hash(canonicalRequest)
|
||||
].join('\n');
|
||||
|
||||
// 计算签名
|
||||
const signature = hmacSha256(token, stringToSign);
|
||||
|
||||
return {
|
||||
timestamp,
|
||||
signature,
|
||||
id
|
||||
};
|
||||
|
||||
// Construct canonical request
|
||||
const canonicalRequest = [
|
||||
method,
|
||||
canonicalPath,
|
||||
query,
|
||||
sha256Hash(body || '')
|
||||
].join('\n');
|
||||
|
||||
// Get current timestamp
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Construct string to sign
|
||||
const stringToSign = [
|
||||
'HMAC-SHA256',
|
||||
timestamp,
|
||||
sha256Hash(canonicalRequest)
|
||||
].join('\n');
|
||||
|
||||
// Calculate signature
|
||||
const signature = hmacSha256(token, stringToSign);
|
||||
|
||||
return {
|
||||
timestamp,
|
||||
signature,
|
||||
id
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送API请求
|
||||
* Send API request
|
||||
*/
|
||||
async function sendApiRequest() {
|
||||
// 示例请求参数
|
||||
const apiUrl = 'http://example.com/entrance/api/user/info';
|
||||
const method = 'GET';
|
||||
const body = ''; // GET请求通常没有请求体
|
||||
const id = 16;
|
||||
const token = 'YourSecretToken';
|
||||
|
||||
try {
|
||||
// 生成签名信息
|
||||
const signingData = signRequest(method, apiUrl, body, id, token);
|
||||
|
||||
// 准备HTTP请求头
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Timestamp': signingData.timestamp,
|
||||
'Authorization': `HMAC-SHA256 Credential=${signingData.id}, Signature=${signingData.signature}`
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
const response = await axios({
|
||||
method,
|
||||
url: apiUrl,
|
||||
headers,
|
||||
data: body || undefined
|
||||
});
|
||||
|
||||
// 输出结果
|
||||
console.log(`响应状态码: ${response.status}`);
|
||||
console.log(`响应内容: ${JSON.stringify(response.data)}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('请求出错:', error.message);
|
||||
if (error.response) {
|
||||
console.error(`响应状态码: ${error.response.status}`);
|
||||
console.error(`响应内容: ${JSON.stringify(error.response.data)}`);
|
||||
// Example request parameters
|
||||
const apiUrl = 'http://example.com/entrance/api/user/info';
|
||||
const method = 'GET';
|
||||
const body = ''; // GET requests typically have no body
|
||||
const id = 16;
|
||||
const token = 'YourSecretToken';
|
||||
|
||||
try {
|
||||
// Generate signature information
|
||||
const signingData = signRequest(method, apiUrl, body, id, token);
|
||||
|
||||
// Prepare HTTP headers
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Timestamp': signingData.timestamp,
|
||||
'Authorization': `HMAC-SHA256 Credential=${signingData.id}, Signature=${signingData.signature}`
|
||||
};
|
||||
|
||||
// Send request
|
||||
const response = await axios({
|
||||
method,
|
||||
url: apiUrl,
|
||||
headers,
|
||||
data: body || undefined
|
||||
});
|
||||
|
||||
// Output results
|
||||
console.log(`Response Status Code: ${response.status}`);
|
||||
console.log(`Response Content: ${JSON.stringify(response.data)}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Request Error:', error.message);
|
||||
if (error.response) {
|
||||
console.error(`Response Status Code: ${error.response.status}`);
|
||||
console.error(`Response Content: ${JSON.stringify(error.response.data)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行请求
|
||||
// Execute request
|
||||
sendApiRequest();
|
||||
```
|
||||
|
||||
## 常见响应码
|
||||
## Common Response Codes
|
||||
|
||||
| HTTP 状态码 | 描述 |
|
||||
|----------|---------|
|
||||
| 200 | 请求成功 |
|
||||
| 401 | 身份验证失败 |
|
||||
| 403 | 权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 422 | 请求参数错误 |
|
||||
| 500 | 服务器内部错误 |
|
||||
| HTTP Status Code | Description |
|
||||
|------------------|-------------------------|
|
||||
| 200 | Request successful |
|
||||
| 401 | Authentication failed |
|
||||
| 403 | Permission denied |
|
||||
| 404 | Resource not found |
|
||||
| 422 | Request parameter error |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## 安全建议
|
||||
## Security Recommendations
|
||||
|
||||
1. **保护您的 API 令牌**:不要在客户端代码中硬编码或公开您的 API 令牌
|
||||
2. **定期轮换令牌**:定期更改您的 API 令牌以提高安全性
|
||||
3. **配置 IP 白名单**:在生产环境中使用 IP 白名单限制访问
|
||||
1. **Protect Your API Token**: Do not hardcode or expose your API token in client-side code
|
||||
2. **Rotate Tokens Regularly**: Change your API token regularly to enhance security
|
||||
3. **Configure IP Whitelisting**: Use IP whitelisting to restrict access in production environments
|
||||
|
||||
## 常见问题解答
|
||||
## Frequently Asked Questions
|
||||
|
||||
### 签名验证失败
|
||||
### Signature Verification Failed
|
||||
|
||||
如果遇到签名验证失败,请检查:
|
||||
If you encounter signature verification failures, check:
|
||||
|
||||
- 确保使用了正确的 API 令牌和 ID
|
||||
- 检查客户端与服务器的时间是否准确,时间戳偏差大于 300 秒会导致验证失败
|
||||
- 确保请求体在计算签名前后没有被修改
|
||||
- 确保 URL 路径处理正确,注意规范化路径时需要移除入口前缀
|
||||
- Ensure you are using the correct API token and ID
|
||||
- Check that the client and server times are accurate; timestamp differences greater than 300 seconds will cause
|
||||
verification to fail
|
||||
- Ensure the request body hasn't been modified before or after signature calculation
|
||||
- Ensure the URL path is handled correctly; remember to remove the entry prefix when normalizing the path
|
||||
|
||||
### 请求超时
|
||||
### Request Timeout
|
||||
|
||||
- 检查网络连接
|
||||
- 确认服务器状态
|
||||
- 考虑增加客户端的超时设置
|
||||
- Check network connection
|
||||
- Confirm server status
|
||||
- Consider increasing the client timeout settings
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# 配置容器镜像加速
|
||||
# Configure Container Image Acceleration
|
||||
|
||||
由于一些原因国内可能无法连接到 Docker Hub 拉取容器镜像,因此需要配置镜像加速。
|
||||
Due to certain reasons, domestic users in China may be unable to connect to Docker Hub to pull container images, thus
|
||||
requiring image acceleration configuration.
|
||||
|
||||
## 对于 Podman
|
||||
## For Podman
|
||||
|
||||
在面板打开 Podman 设置页面,导航到注册表配置选项卡。
|
||||
Open the Podman settings page in the Panel, and navigate to the Registry Configuration tab.
|
||||
|
||||
滚动到配置文件底部,添加如下配置并保存:
|
||||
Scroll to the bottom of the configuration file, add the following configuration and save:
|
||||
|
||||
```
|
||||
[[registry]]
|
||||
@@ -15,13 +16,14 @@ location = "docker.io"
|
||||
location = "docker.1ms.run"
|
||||
```
|
||||
|
||||
其中 docker.1ms.run 为配置的镜像加速地址,可自行参考其他教程搭建使用。
|
||||
Where docker.1ms.run is the configured image acceleration address. You can refer to other tutorials to set up and use
|
||||
it.
|
||||
|
||||
## 对于 Docker
|
||||
## For Docker
|
||||
|
||||
在面板打开 Docker 设置页面,导航到配置选项卡。
|
||||
Open the Docker settings page in the Panel, and navigate to the Configuration tab.
|
||||
|
||||
添加如下配置并保存:
|
||||
Add the following configuration and save:
|
||||
|
||||
```
|
||||
{
|
||||
@@ -29,4 +31,5 @@ location = "docker.1ms.run"
|
||||
}
|
||||
```
|
||||
|
||||
其中 https://docker.1ms.run 为配置的镜像加速地址,可自行参考其他教程搭建使用。
|
||||
Where https://docker.1ms.run is the configured image acceleration address. You can refer to other tutorials to set up
|
||||
and use it.
|
||||
@@ -1,5 +1,7 @@
|
||||
# 配置反向代理
|
||||
# Configure Reverse Proxy
|
||||
|
||||
面板 v2.4.10+ 自带反向代理配置生成器,你可以通过站点伪静态配置页面的右上角打开使用。
|
||||
RatPanel v2.4.10+ comes with a built-in reverse proxy configuration generator, which you can access through the top
|
||||
right corner of the site rewrite configuration page.
|
||||
|
||||
注意:如果设置反向代理后出现 CSS/JS 等静态资源无法正常加载的问题,请移除站点主配置文件中的**不记录静态文件日志**部分。
|
||||
Note: If you encounter issues with static resources like CSS/JS not loading properly after setting up a reverse proxy,
|
||||
please remove the **Do not log static files** section from the site's main configuration file.
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# 配置 QUIC(HTTP3)
|
||||
# Configure QUIC (HTTP3)
|
||||
|
||||
面板目前已支持自动 QUIC 配置,但是出于兼容性考虑,默认未添加 `Alt-Svc` 标头,浏览器在未检测到 `Alt-Svc` 标头时不会尝试使用 QUIC 连接。
|
||||
RatPanel currently supports automatic QUIC configuration, but for compatibility reasons, the `Alt-Svc` header is not
|
||||
added by default. Browsers will not attempt to use QUIC connections without detecting the `Alt-Svc` header.
|
||||
|
||||
如果你不使用 CDN,可添加下述配置到网站伪静态中即可让浏览器知晓网站支持并使用 QUIC 连接。
|
||||
If you are not using a CDN, you can add the configuration below to your website's rewrite rules to let browsers know
|
||||
that the website supports and uses QUIC connections.
|
||||
|
||||
```
|
||||
add_header Alt-Svc 'h3=":$server_port"; ma=2592000';
|
||||
```
|
||||
|
||||
如果你使用 CDN 或者前端还存在代理服务器,则 QUIC 需要在 CDN / 前端开启。
|
||||
如果配置后仍不生效,请检查浏览器版本和 UDP 443 端口的可用性。
|
||||
If you are using a CDN or there are proxy servers in front, then QUIC needs to be enabled on the CDN / frontend.
|
||||
If the configuration still doesn't work, please check your browser version and the availability of UDP port 443.
|
||||
|
||||
* 根据 Nginx 的 git 提交记录,1.25 版本下所有 QUIC 草案版本已经移除,因此 `Alt-Svc` 无需添加草案版本号。
|
||||
* According to Nginx's git commit history, all QUIC draft versions have been removed in version 1.25, so there's no need
|
||||
to add draft version numbers to `Alt-Svc`.
|
||||
|
||||
@@ -1,32 +1,45 @@
|
||||
# 安全性建议
|
||||
# Security Recommendations
|
||||
|
||||
通过以下安全措施,几乎可以杜绝一切被黑/挂马问题。
|
||||
With the following security measures, almost all hacking/malware issues can be prevented.
|
||||
|
||||
### 网站方面
|
||||
### Website Aspects
|
||||
|
||||
根据以往经验大多数被黑挂马都是程序漏洞造成的,与面板等环境无关,为了网站安全,你应该做到:
|
||||
Based on past experience, most hacking and malware incidents are caused by program vulnerabilities, unrelated to the
|
||||
Panel or environment. For website security, you should:
|
||||
|
||||
1. 不要使用盗版程序、软件,特别是在你无法确定有没有被人加过料的情况下。
|
||||
2. 定期更新网站程序和软件环境,不要怕麻烦使用过时的软件程序,它们的安全性无法保证。
|
||||
3. 网站后台禁止使用弱密码,密码强烈建议使用随机生成器生成大于 20 位的混合密码并保存在安全位置,有条件建议开启程序的多因素身份验证(2FA)。
|
||||
4. 设置定时备份全站数据,不要裸奔。
|
||||
5. PHP 默认禁用了部分高危函数,非必要请勿删除。
|
||||
1. Avoid using pirated programs or software, especially when you cannot determine if they have been tampered with.
|
||||
2. Regularly update website programs and software environments; don't use outdated software due to inconvenience, as
|
||||
their security cannot be guaranteed.
|
||||
3. Never use weak passwords for website admin areas. Passwords are strongly recommended to be generated using a random
|
||||
generator with more than 20 mixed characters and stored in a secure location. If possible, enable multi-factor
|
||||
authentication (2FA) for your programs.
|
||||
4. Set up scheduled backups of all site data; don't operate without backups.
|
||||
5. PHP has disabled some high-risk functions by default; don't remove these restrictions unless absolutely necessary.
|
||||
|
||||
### 系统方面
|
||||
### System Aspects
|
||||
|
||||
现代系统出现严重安全漏洞的概率是很低的,但是你仍应该做到:
|
||||
The probability of serious security vulnerabilities in modern systems is low, but you should still:
|
||||
|
||||
1. 定期更新系统软件。(使用 `yum update` 或 `apt upgrade`)。
|
||||
2. SSH 禁止使用弱密码和默认 22 端口,密码强烈建议使用随机生成器生成大于 20 位的混合密码并保存在安全位置,如果可以建议安装 Fail2ban 针对性保护。
|
||||
3. 不要随意给 777 权限和 www 用户的执行权限,可能造成极大安全隐患。
|
||||
4. 如果运营商提供 VNC 管理服务器,也可以考虑关闭 SSH,从源头上解决问题。
|
||||
1. Regularly update system software. (Use `yum update` or `apt upgrade`).
|
||||
2. Prohibit weak passwords and the default port 22 for SSH. Passwords are strongly recommended to be generated using a
|
||||
random generator with more than 20 mixed characters and stored in a secure location. If possible, consider installing
|
||||
Fail2ban for targeted protection.
|
||||
3. Don't arbitrarily assign 777 permissions or execution permissions to the www user, as this may cause major security
|
||||
risks.
|
||||
4. If your service provider offers VNC server management, consider disabling SSH to solve the problem at the source.
|
||||
|
||||
### 面板方面
|
||||
### Panel Aspects
|
||||
|
||||
面板拥有和 root 一样的权限,管理不当亦会造成严重安全问题,你应该做到:
|
||||
RatPanel has the same privileges as root, and improper management can cause serious security problems. You should:
|
||||
|
||||
1. 定期更新面板及面板安装的应用。同时推荐关注我们的频道或者群,以第一时间接收各类更新消息。
|
||||
2. 面板禁止使用弱密码和默认 8888 端口,密码强烈建议使用随机生成器生成大于 20 位的混合密码并保存在安全位置。
|
||||
3. 建议修改添加面板入口和开启面板 HTTPS,防止被扫描器扫描和中间人攻击。
|
||||
4. 防火墙无必要请不要放行内部服务的端口(Redis 6379、MySQL 3306、PostgreSQL 5432等),可能造成严重安全隐患。(网站本地连接不需要放行,连不上是程序的问题)。
|
||||
5. 对安全性要求较高的情况下,可以考虑日常停止面板的运行,按需启动(面板停止运行不会影响网站、计划任务等的运行)。
|
||||
1. Regularly update the Panel and applications installed through it. We recommend following our channel or group to
|
||||
receive various update messages promptly.
|
||||
2. Prohibit weak passwords and the default 8888 port for the Panel. Passwords are strongly recommended to be generated
|
||||
using a random generator with more than 20 mixed characters and stored in a secure location.
|
||||
3. Consider modifying the Panel entry point and enabling HTTPS for the Panel to prevent scanner detection and
|
||||
man-in-the-middle attacks.
|
||||
4. Unless necessary, do not allow firewall access to internal service ports (Redis 6379, MySQL 3306, PostgreSQL 5432,
|
||||
etc.), as this may cause serious security risks. (Local website connections don't require firewall access; connection
|
||||
issues are program problems).
|
||||
5. For high-security requirements, consider stopping the Panel operation routinely and starting it only when needed (
|
||||
stopping the Panel will not affect websites, scheduled tasks, etc.).
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# 配置进程守护
|
||||
# Configure Process Monitoring
|
||||
|
||||
1. 安装 Supervisor 管理器并打开。
|
||||
2. Supervisor 管理器中创建需要守护的进程(运行用户不建议使用 root)。
|
||||
3. 常见问题:[https://tom.moe/t/supervisor/3112](https://tom.moe/t/supervisor/3112)
|
||||
1. Install Supervisor manager and open it.
|
||||
2. Create processes that need to be monitored in the Supervisor manager (it's not recommended to use root as the running
|
||||
user).
|
||||
3. Common issues: [https://tom.moe/t/supervisor/3112](https://tom.moe/t/supervisor/3112)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# 配置 TLSv1.1 TLSv1
|
||||
# Configure TLSv1.1 TLSv1
|
||||
|
||||
当前面板OpenResty使用OpenSSL 3.0版本编译,默认禁用已弃用的TLSv1.1 TLSv1协议。
|
||||
The current Panel OpenResty is compiled with OpenSSL 3.5, which by default disables the deprecated TLSv1.1 and TLSv1
|
||||
protocols.
|
||||
|
||||
当然,如果你的业务必须要使用这两个协议的话,可以使用下述SSL配置启用。
|
||||
Of course, if your business must use these two protocols, you can enable them using the SSL configuration below.
|
||||
|
||||
```
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
|
||||
67
en/cert.md
@@ -4,11 +4,13 @@ prev: false
|
||||
next: false
|
||||
---
|
||||
|
||||
# 证书
|
||||
# Certificates
|
||||
|
||||
若 3 个月免费证书无法满足您的需求,您可以选择购买更高级别的证书。我们提供 DV 单域名证书和 DV 通配符证书可供选择。
|
||||
If the 3-month free certificate cannot meet your needs, you can choose to purchase higher-level certificates. We offer
|
||||
DV single-domain certificates and DV wildcard certificates for your selection.
|
||||
|
||||
受限价政策,具体价格及购买请直接通过右上角加群联系销售。
|
||||
Due to pricing policy restrictions, please contact our sales team directly through the group link in the upper right
|
||||
corner for specific prices and purchases.
|
||||
|
||||
<style>
|
||||
.cert-cards-container {
|
||||
@@ -22,6 +24,8 @@ next: false
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cert-card-header {
|
||||
@@ -35,6 +39,9 @@ next: false
|
||||
margin: 0!important;
|
||||
border-top: unset!important;
|
||||
padding-top: unset!important;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cert-subtitle {
|
||||
@@ -53,6 +60,7 @@ next: false
|
||||
|
||||
.cert-features {
|
||||
margin-bottom: 24px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.cert-feature-item {
|
||||
@@ -71,6 +79,7 @@ next: false
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.cert-buy-button:hover {
|
||||
@@ -112,93 +121,93 @@ next: false
|
||||
}
|
||||
</style>
|
||||
|
||||
## 选购证书
|
||||
## Choose Certificates
|
||||
|
||||
<div class="cert-cards-container">
|
||||
<div class="cert-card">
|
||||
<div class="cert-card-header">
|
||||
<h2 class="cert-card-title">DV 单域名证书</h2>
|
||||
<h2 class="cert-card-title">DV Single-domain Certificate</h2>
|
||||
</div>
|
||||
<div class="cert-subtitle">国际知名品牌证书</div>
|
||||
<div class="cert-price">¥1X<span>/年</span></div>
|
||||
<div class="cert-subtitle">Internationally recognized brand certificate</div>
|
||||
<div class="cert-price">¥ 1X<span>/year</span></div>
|
||||
<div class="cert-features">
|
||||
<div class="cert-feature-item">
|
||||
<span class="cert-check-icon">✓</span>
|
||||
<span>域名型证书(DV)</span>
|
||||
<span>Domain Validation (DV) Certificate</span>
|
||||
</div>
|
||||
<div class="cert-feature-item">
|
||||
<span class="cert-check-icon">✓</span>
|
||||
<span>支持一个域名</span>
|
||||
<span>Supports one domain</span>
|
||||
</div>
|
||||
<div class="cert-feature-item">
|
||||
<span class="cert-check-icon">✓</span>
|
||||
<span>一年有效期</span>
|
||||
<span>One-year validity</span>
|
||||
</div>
|
||||
<div class="cert-feature-item">
|
||||
<span class="cert-check-icon">✓</span>
|
||||
<span>快速颁发</span>
|
||||
<span>Rapid issuance</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="cert-buy-button">右上角联系销售购买</button>
|
||||
<button class="cert-buy-button">Contact sales in the upper right corner</button>
|
||||
</div>
|
||||
<div class="cert-card">
|
||||
<div class="cert-card-header">
|
||||
<h2 class="cert-card-title">DV 通配符证书</h2>
|
||||
<h2 class="cert-card-title">DV Wildcard Certificate</h2>
|
||||
</div>
|
||||
<div class="cert-subtitle">国际知名品牌证书</div>
|
||||
<div class="cert-price">¥1XX<span>/年</span></div>
|
||||
<div class="cert-subtitle">Internationally recognized brand certificate</div>
|
||||
<div class="cert-price">¥ 1XX<span>/year</span></div>
|
||||
<div class="cert-features">
|
||||
<div class="cert-feature-item">
|
||||
<span class="cert-check-icon">✓</span>
|
||||
<span>域名型证书(DV)</span>
|
||||
<span>Domain Validation (DV) Certificate</span>
|
||||
</div>
|
||||
<div class="cert-feature-item">
|
||||
<span class="cert-check-icon">✓</span>
|
||||
<span>通配所有子域</span>
|
||||
<span>Covers all subdomains</span>
|
||||
</div>
|
||||
<div class="cert-feature-item">
|
||||
<span class="cert-check-icon">✓</span>
|
||||
<span>一年有效期</span>
|
||||
<span>One-year validity</span>
|
||||
</div>
|
||||
<div class="cert-feature-item">
|
||||
<span class="cert-check-icon">✓</span>
|
||||
<span>快速颁发</span>
|
||||
<span>Rapid issuance</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="cert-buy-button">右上角联系销售购买</button>
|
||||
<button class="cert-buy-button">Contact sales in the upper right corner</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## 证书特点
|
||||
## Certificate Features
|
||||
|
||||
<div class="cert-features-section">
|
||||
<div class="cert-features-grid">
|
||||
<div class="cert-feature-card">
|
||||
<div class="cert-feature-icon">🛡️</div>
|
||||
<div class="cert-feature-content">
|
||||
<h3>安全可靠</h3>
|
||||
<p>采用国际标准,保护网站安全</p>
|
||||
<h3>Safe and Reliable</h3>
|
||||
<p>Uses international standards to protect website security</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cert-feature-card">
|
||||
<div class="cert-feature-icon">⚡</div>
|
||||
<div class="cert-feature-content">
|
||||
<h3>快速签发</h3>
|
||||
<p>简化签发流程,快速颁发证书</p>
|
||||
<h3>Rapid Issuance</h3>
|
||||
<p>Simplified issuance process for quick certificate delivery</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cert-feature-card">
|
||||
<div class="cert-feature-icon">💳</div>
|
||||
<div class="cert-feature-content">
|
||||
<h3>灵活付费</h3>
|
||||
<p>多种规格可选,满足不同需求</p>
|
||||
<h3>Flexible Payment</h3>
|
||||
<p>Multiple specifications available to meet different needs</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cert-feature-card">
|
||||
<div class="cert-feature-icon">👨💻</div>
|
||||
<div class="cert-feature-content">
|
||||
<h3>专业服务</h3>
|
||||
<p>专业技术支持,保障使用无忧</p>
|
||||
<h3>Professional Service</h3>
|
||||
<p>Professional technical support ensures worry-free usage</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,18 +49,18 @@ features:
|
||||
|
||||
<div style="display: flex; justify-content: space-around; align-items: center; flex-wrap: wrap;">
|
||||
<a href="https://www.weixiaoduo.com/" style="padding: 1rem;">
|
||||
<img width="160" src="/wxd.png" alt="微晓朵">
|
||||
<img width="160" src="/wxd.png" alt="WeiXiaoDuo">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ" style="padding: 1rem;">
|
||||
<img width="160" src="/dk.png" alt="林枫云">
|
||||
<img width="160" src="/dk.png" alt="LinFeng Cloud">
|
||||
</a>
|
||||
<a href="https://waf.pro/">
|
||||
<img width="160" src="/wafpro.png" alt="WAFPRO" style="padding: 1rem;">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img width="160" src="/ddunyun.png" alt="盾云SCDN" style="padding: 1rem;">
|
||||
<img width="160" src="/ddunyun.png" alt="DunYun SCDN" style="padding: 1rem;">
|
||||
</a>
|
||||
<a href="https://1ms.run/" style="padding: 1rem;">
|
||||
<img width="160" src="/1ms.svg" alt="毫秒镜像">
|
||||
<img width="160" src="/1ms.svg" alt="Millisecond Mirror">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
# 管理容器
|
||||
# Manage Containers
|
||||
|
||||
开始前需安装 Docker / Podman 容器引擎。
|
||||
Before starting, you need to install the Docker / Podman container engine.
|
||||
|
||||
容器引擎安装完毕后,即可前往面板容器管理处创建容器(此处以 pgadmin4 为例)。
|
||||
After the container engine is installed, you can go to the Panel's container management section to create containers (
|
||||
pgadmin4 is used as an example here).
|
||||
|
||||
首先导航到镜像选项卡拉取需要的镜像,视网络环境可能需要数分钟到数十分钟。
|
||||
First, navigate to the Images tab to pull the required images. This may take several minutes to tens of minutes
|
||||
depending on your network environment.
|
||||
|
||||

|
||||

|
||||
|
||||
镜像拉取完成后,导航到容器选项卡开始创建容器。
|
||||
After the image is pulled, navigate to the Containers tab to start creating a container.
|
||||
|
||||

|
||||

|
||||
|
||||
表单内容按容器的说明填写,此处使用的 pgadmin4 镜像需要映射 80 端口及配置 2 个默认环境变量,无需映射目录。
|
||||
Fill in the form according to the container's instructions. The pgadmin4 image used here needs to map port 80 and
|
||||
configure 2 default environment variables, with no need to map directories.
|
||||
|
||||
容器创建完成后可点击右上角刷新按钮和容器右侧的日志按钮检查是否正常启动。
|
||||
After the container is created, you can click the refresh button in the upper right corner and the log button on the
|
||||
right side of the container to check if it has started properly.
|
||||
|
||||

|
||||

|
||||
|
||||
若容器未能启动,请根据日志进行修正。
|
||||
如果映射了外部端口,需要到防火墙菜单放行相应的端口。
|
||||
If the container fails to start, please make corrections according to the logs.
|
||||
If you have mapped external ports, you need to go to the firewall menu to allow the corresponding ports.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# 挂载分区
|
||||
# Mount Partitions
|
||||
|
||||
如果您的服务器有未挂载的数据盘,可在安装前以 `root` 用户登录服务器运行以下命令自动挂载,面板安装后不支持跨目录迁移。
|
||||
If your server has unmounted data disks, you can log in as `root` user before installation and run the following command
|
||||
to automatically mount them. Cross-directory migration is not supported after Panel installation.
|
||||
|
||||
```shell
|
||||
curl -fsLm 10 -o auto_mount.sh https://dl.cdn.haozi.net/panel/auto_mount.sh && bash auto_mount.sh
|
||||
```
|
||||
|
||||
也可工单联系服务器提供商要求协助挂载分区,或者自行挂载分区后再安装面板。
|
||||
You can also contact your server provider via ticket to request assistance with mounting partitions, or mount the
|
||||
partitions yourself before installing the Panel.
|
||||
|
||||
@@ -1,40 +1,45 @@
|
||||
# 安装面板
|
||||
# Install Panel
|
||||
|
||||
面板支持 `amd64` | `arm64` 架构下的主流系统,下表中的系统均已测试 LNMP 环境安装。
|
||||
The Panel supports mainstream systems under `amd64` | `arm64` architectures. LNMP environment installation has been
|
||||
tested on all systems listed in the table below.
|
||||
|
||||
优先建议使用标注**推荐**的系统,无特殊情况不建议使用标注**不推荐**的系统。
|
||||
It is recommended to use systems marked as **recommended**. Unless there are special circumstances, it is not
|
||||
recommended to use systems marked as **not recommended**.
|
||||
|
||||
不在下表中的其他系统,可自行尝试安装,但不提供无偿技术支持。
|
||||
For systems not listed in the table below, you can try installing on your own, but no free technical support will be
|
||||
provided.
|
||||
|
||||
| 系统 | 版本 | 备注 |
|
||||
|---------------------|-----|-----|
|
||||
| AlmaLinux | 9 | 推荐 |
|
||||
| AlmaLinux | 8 | 不推荐 |
|
||||
| RockyLinux | 9 | 支持 |
|
||||
| RockyLinux | 8 | 不推荐 |
|
||||
| CentOS Stream | 9 | 不推荐 |
|
||||
| CentOS Stream | 8 | 不推荐 |
|
||||
| Ubuntu | 24 | 推荐 |
|
||||
| Ubuntu | 22 | 支持 |
|
||||
| Debian | 12 | 推荐 |
|
||||
| Debian | 11 | 支持 |
|
||||
| OpenCloudOS | 9 | 支持 |
|
||||
| TencentOS Server | 4 | 支持 |
|
||||
| TencentOS Server | 3.1 | 不推荐 |
|
||||
| Alibaba Cloud Linux | 3.2 | 不推荐 |
|
||||
| Anolis | 8 | 不推荐 |
|
||||
| openEuler | 22 | 不推荐 |
|
||||
| System | Version | Note |
|
||||
|---------------------|---------|-----------------|
|
||||
| AlmaLinux | 9 | Recommended |
|
||||
| AlmaLinux | 8 | Not Recommended |
|
||||
| RockyLinux | 9 | Supported |
|
||||
| RockyLinux | 8 | Not Recommended |
|
||||
| CentOS Stream | 9 | Not Recommended |
|
||||
| CentOS Stream | 8 | Not Recommended |
|
||||
| Ubuntu | 24 | Recommended |
|
||||
| Ubuntu | 22 | Supported |
|
||||
| Debian | 12 | Recommended |
|
||||
| Debian | 11 | Supported |
|
||||
| OpenCloudOS | 9 | Supported |
|
||||
| TencentOS Server | 4 | Supported |
|
||||
| TencentOS Server | 3.1 | Not Recommended |
|
||||
| Alibaba Cloud Linux | 3.2 | Not Recommended |
|
||||
| Anolis | 8 | Not Recommended |
|
||||
| openEuler | 22 | Not Recommended |
|
||||
|
||||
随着系统版本的不断更新,我们亦可能会终止部分过于老旧的系统的支持,以保证面板的健壮性。
|
||||
As system versions continue to update, we may also terminate support for some overly outdated systems to ensure the
|
||||
robustness of the Panel.
|
||||
|
||||
## 开始安装
|
||||
## Start Installation
|
||||
|
||||
> 如需挂载分区,请在安装面板前完成,面板安装后不支持跨目录迁移。
|
||||
> If you need to mount partitions, please complete before installing the Panel. Cross-directory migration is not
|
||||
> supported after Panel installation.
|
||||
|
||||
以 `root` 用户登录服务器,运行以下命令安装面板:
|
||||
Log in to the server as `root` user and run the following command to install the Panel:
|
||||
|
||||
```shell
|
||||
curl -fsLm 10 -o install.sh https://dl.cdn.haozi.net/panel/install.sh && bash install.sh
|
||||
```
|
||||
|
||||
一般 2 分钟内即可完成安装,安装过程中请勿关闭终端。
|
||||
Installation is usually completed within 2 minutes. Do not close the terminal during the installation process.
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
# 管理面板
|
||||
# Manage Panel
|
||||
|
||||
请勿在面板仍有任务运行时操作停止 / 重启面板,否则可能会造成问题。
|
||||
Do not stop or restart the Panel while tasks are still running, as this may cause issues.
|
||||
|
||||
* 启动面板:`systemctl start panel`
|
||||
* 停止面板:`systemctl stop panel`
|
||||
* 重启面板:`systemctl restart panel`
|
||||
* Start Panel: `systemctl start panel`
|
||||
* Stop Panel: `systemctl stop panel`
|
||||
* Restart Panel: `systemctl restart panel`
|
||||
|
||||
## 面板命令行
|
||||
## Panel Command Line
|
||||
|
||||
```bash
|
||||
panel-cli
|
||||
```
|
||||
|
||||
可根据提示补全需要的命令进行操作。
|
||||
Follow the prompts to complete the necessary commands for operation.
|
||||
|
||||
## 卸载面板
|
||||
## Uninstall Panel
|
||||
|
||||
优先建议备份数据重装系统,这样可以保证系统纯净。
|
||||
It is primarily recommended to back up data and reinstall the system, as this ensures a clean system.
|
||||
|
||||
如果您无法重装系统,请以`root`用户登录服务器,执行以下命令卸载面板:
|
||||
If you are unable to reinstall the system, please log in to the server as `root` user and execute the following command
|
||||
to uninstall the Panel:
|
||||
|
||||
```shell
|
||||
curl -fsLm 10 -o uninstall.sh https://dl.cdn.haozi.net/panel/uninstall.sh && bash uninstall.sh
|
||||
```
|
||||
|
||||
卸载面板前请务必备份好所有数据,提前卸载面板全部应用。卸载后数据将**无法恢复**!
|
||||
Before uninstalling the Panel, be sure to back up all data and uninstall all Panel applications in advance. Data will be
|
||||
**unrecoverable** after uninstallation!
|
||||
|
||||
@@ -4,34 +4,35 @@ prev: false
|
||||
next: false
|
||||
---
|
||||
|
||||
# 支持
|
||||
# Support
|
||||
|
||||
## 免费论坛服务
|
||||
## Free Forum Service
|
||||
|
||||
我们提供免费的论坛服务,您可以在论坛中提问、交流、分享面板使用中的问题。
|
||||
We provide a free forum service where you can ask questions, discuss, and share issues related to using the Panel.
|
||||
|
||||
- [Moe Tom](https://tom.moe)
|
||||
|
||||
## 付费远程服务
|
||||
## Paid Remote Service
|
||||
|
||||
网站报错、速度慢、被挂马?服务器资源占用高、配置调优、入侵朔源?我们统统一站式服务。
|
||||
Website errors, slow speed, malware infections? High server resource usage, configuration optimization, intrusion
|
||||
tracing? We provide all these services in one place.
|
||||
|
||||
### 远程服务价格表
|
||||
### Remote Service Price List
|
||||
|
||||
远程服务是指我们通过远程协助的方式,帮助您解决问题。
|
||||
Remote service refers to our assistance in solving your problems through remote support.
|
||||
|
||||
| 名称 | 介绍 | 价格 |
|
||||
|------|------------------|-------|
|
||||
| 单次服务 | 仅限一次服务,问题解决服务即结束 | ¥50 起 |
|
||||
| Service Name | Description | Price |
|
||||
|----------------|-------------------------------------------------------------------------|----------|
|
||||
| Single Service | Limited to one service session, service ends when the issue is resolved | From ¥50 |
|
||||
|
||||
### 服务流程
|
||||
### Service Process
|
||||
|
||||
1. 提前联系我们,说明问题
|
||||
2. 我们评估问题,确认是否可以解决及给出预估价格
|
||||
3. 您确认价格后,支付 50% 预付款,服务开始
|
||||
4. 服务完成后,您确认无误并支付剩余款项
|
||||
1. Contact us in advance and explain the issue
|
||||
2. We evaluate the problem, confirm whether it can be resolved, and provide an estimated price
|
||||
3. After you confirm the price, pay 50% upfront, and the service begins
|
||||
4. After service completion, you confirm everything is in order and pay the remaining amount
|
||||
|
||||
## 联系我们
|
||||
## Contact Us
|
||||
|
||||
- [企业微信](https://work.weixin.qq.com/kfid/kfc20ea8e38b5a4e73a)
|
||||
- [WeChat Work](https://work.weixin.qq.com/kfid/kfc20ea8e38b5a4e73a)
|
||||
- [QQ 826896000](https://wpa.qq.com/msgrd?v=3&uin=826896000&site=qq&menu=yes)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# v{{ $params.version }}
|
||||
|
||||
- 版本类型:{{ $params.type == 'stable' ? '稳定版' : '测试版' }}
|
||||
- 发布时间:{{ $params.time }}
|
||||
- Version Type: {{ $params.type == 'stable' ? 'Stable' : 'Beta' }}
|
||||
- Release Time: {{ $params.time }}
|
||||
|
||||
## 更新内容
|
||||
## Updates log
|
||||
|
||||
<!-- @content -->
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |