mirror of
https://github.com/arch3rPro/1Panel-Appstore.git
synced 2026-04-15 00:17:12 +08:00
feat: add AI-powered 1Panel app builder skill
- Add skill configuration for generating 1Panel app configs via AI - Include templates for data.yml and docker-compose.yml - Add utility scripts for app generation, icon download, and validation - Provide reference examples and usage documentation - Update .gitignore to exclude .trae directory - Update README.md with skill usage instructions
This commit is contained in:
195
skills/scripts/download-icon.sh
Normal file
195
skills/scripts/download-icon.sh
Normal file
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# =============================================================================
|
||||
# Icon Downloader - 从多个图标源下载应用图标
|
||||
# 支持的源:dashboardicons、simpleicons、selfh.st
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 图标源配置
|
||||
DASHBOARD_ICONS="https://dashboardicons.com/icons"
|
||||
SIMPLE_ICONS="https://cdn.simpleicons.org"
|
||||
SELFHST_ICONS="https://selfh.st/icons"
|
||||
|
||||
# 默认输出尺寸
|
||||
DEFAULT_SIZE=200
|
||||
|
||||
# 打印帮助
|
||||
print_help() {
|
||||
cat << EOF
|
||||
${BLUE}Icon Downloader${NC} - 从多个图标源下载应用图标
|
||||
|
||||
${YELLOW}用法:${NC}
|
||||
$0 <应用名称> [输出文件] [尺寸]
|
||||
|
||||
${YELLOW}参数:${NC}
|
||||
应用名称 - 应用的名称(如 alist, nginx, redis)
|
||||
输出文件 - 图标保存路径(默认: ./logo.png)
|
||||
尺寸 - 图标尺寸(默认: 200x200)
|
||||
|
||||
${YELLOW}图标源优先级:${NC}
|
||||
1. Dashboard Icons (dashboardicons.com)
|
||||
2. Simple Icons (simpleicons.org)
|
||||
3. selfh.st Icons (selfh.st)
|
||||
|
||||
${YELLOW}示例:${NC}
|
||||
$0 alist ./logo.png 200
|
||||
$0 nginx /tmp/nginx-icon.png
|
||||
$0 redis
|
||||
EOF
|
||||
}
|
||||
|
||||
# 日志函数
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# 下载并调整图标尺寸
|
||||
download_and_resize() {
|
||||
local url="$1"
|
||||
local output="$2"
|
||||
local size="$3"
|
||||
|
||||
if curl -sSL -o "$output" "$url" 2>/dev/null && [[ -s "$output" ]]; then
|
||||
# 检查是否安装了 ImageMagick 或 sips (macOS)
|
||||
if command -v convert &>/dev/null; then
|
||||
convert "$output" -resize "${size}x${size}" "$output" 2>/dev/null || true
|
||||
elif command -v sips &>/dev/null; then
|
||||
sips -z "$size" "$size" "$output" &>/dev/null || true
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# 尝试从各图标源下载
|
||||
try_download_icon() {
|
||||
local app_name="$1"
|
||||
local output="$2"
|
||||
local size="$3"
|
||||
|
||||
local name_lower
|
||||
name_lower=$(echo "$app_name" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
|
||||
|
||||
log_info "尝试下载图标: $app_name"
|
||||
|
||||
# 1. Dashboard Icons
|
||||
log_info "尝试 Dashboard Icons..."
|
||||
local dashboard_url="${DASHBOARD_ICONS}/${name_lower}.png"
|
||||
if download_and_resize "$dashboard_url" "$output" "$size"; then
|
||||
log_info "✓ 从 Dashboard Icons 下载成功"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 尝试带 -dark 后缀
|
||||
if download_and_resize "${DASHBOARD_ICONS}/${name_lower}-dark.png" "$output" "$size"; then
|
||||
log_info "✓ 从 Dashboard Icons (dark) 下载成功"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 2. Simple Icons
|
||||
log_info "尝试 Simple Icons..."
|
||||
local simple_url="${SIMPLE_ICONS}/${name_lower}"
|
||||
if download_and_resize "$simple_url" "$output" "$size"; then
|
||||
log_info "✓ 从 Simple Icons 下载成功"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Simple Icons 也支持 SVG
|
||||
if curl -sSL -o "${output%.png}.svg" "${SIMPLE_ICONS}/${name_lower}" 2>/dev/null && [[ -s "${output%.png}.svg" ]]; then
|
||||
log_info "✓ 从 Simple Icons 下载 SVG 成功"
|
||||
# 如果有 SVG,尝试转换
|
||||
if command -v convert &>/dev/null; then
|
||||
convert "${output%.png}.svg" -resize "${size}x${size}" "$output" 2>/dev/null || true
|
||||
if [[ -s "$output" ]]; then
|
||||
rm -f "${output%.png}.svg"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. selfh.st Icons
|
||||
log_info "尝试 selfh.st Icons..."
|
||||
local selfhst_url="${SELFHST_ICONS}/${name_lower}.png"
|
||||
if download_and_resize "$selfhst_url" "$output" "$size"; then
|
||||
log_info "✓ 从 selfh.st Icons 下载成功"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# 创建占位图标
|
||||
create_placeholder() {
|
||||
local output="$1"
|
||||
local size="$2"
|
||||
|
||||
log_warn "创建占位图标"
|
||||
|
||||
# 使用 ImageMagick 创建简单的占位图标
|
||||
if command -v convert &>/dev/null; then
|
||||
convert -size "${size}x${size}" xc:'#4A90E2' \
|
||||
-gravity center \
|
||||
-pointsize 48 \
|
||||
-fill white \
|
||||
-annotate 0 "?" \
|
||||
"$output" 2>/dev/null
|
||||
return $?
|
||||
fi
|
||||
|
||||
# macOS sips 方式
|
||||
if command -v sips &>/dev/null; then
|
||||
# 创建一个简单的 1x1 像素 PNG 然后放大
|
||||
local temp_file="/tmp/placeholder_${RANDOM}.png"
|
||||
echo -n "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" | base64 -d > "$temp_file"
|
||||
sips -z "$size" "$size" "$temp_file" --out "$output" &>/dev/null
|
||||
rm -f "$temp_file"
|
||||
return $?
|
||||
fi
|
||||
|
||||
# 最简单的占位符
|
||||
echo -n "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" | base64 -d > "$output"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local app_name="${1:-}"
|
||||
local output="${2:-./logo.png}"
|
||||
local size="${3:-$DEFAULT_SIZE}"
|
||||
|
||||
# 参数检查
|
||||
if [[ -z "$app_name" ]] || [[ "$app_name" == "-h" ]] || [[ "$app_name" == "--help" ]]; then
|
||||
print_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 确保输出目录存在
|
||||
local output_dir
|
||||
output_dir=$(dirname "$output")
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
# 尝试下载
|
||||
if try_download_icon "$app_name" "$output" "$size"; then
|
||||
log_info "${GREEN}✓ 图标下载完成: $output${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 下载失败,创建占位符
|
||||
create_placeholder "$output" "$size"
|
||||
log_warn "未找到图标,已创建占位符: $output"
|
||||
log_info "请手动从以下网站下载合适的图标:"
|
||||
echo " - https://dashboardicons.com/icons?q=${app_name}"
|
||||
echo " - https://simpleicons.org/?q=${app_name}"
|
||||
echo " - https://selfh.st/icons/"
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
416
skills/scripts/generate-app.sh
Normal file
416
skills/scripts/generate-app.sh
Normal file
@@ -0,0 +1,416 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# =============================================================================
|
||||
# 1Panel App Builder - 主生成脚本
|
||||
# 功能:从 GitHub 项目、docker-compose、docker run 命令生成 1Panel APP 配置
|
||||
# 用法:./generate-app.sh <输入源> [输出目录]
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 默认配置
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TEMPLATE_DIR="${SCRIPT_DIR}/../templates"
|
||||
OUTPUT_BASE="${2:-./apps}"
|
||||
ICON_SOURCES=(
|
||||
"https://dashboardicons.com/icons"
|
||||
"https://simpleicons.org/icons"
|
||||
"https://selfh.st/icons"
|
||||
)
|
||||
|
||||
# 打印帮助
|
||||
print_help() {
|
||||
cat << EOF
|
||||
${BLUE}1Panel App Builder${NC} - 快速生成 1Panel 应用配置
|
||||
|
||||
${YELLOW}用法:${NC}
|
||||
$0 <输入源> [输出目录]
|
||||
|
||||
${YELLOW}输入源支持:${NC}
|
||||
1. GitHub 项目链接
|
||||
例如: https://github.com/alist-org/alist
|
||||
|
||||
2. docker-compose.yml 文件链接
|
||||
例如: https://raw.githubusercontent.com/alist-org/alist/master/docker-compose.yml
|
||||
|
||||
3. docker run 命令
|
||||
例如: "docker run -d --name=alist -p 5244:5244 xhofe/alist:v3.45.0"
|
||||
|
||||
4. 本地文件路径
|
||||
例如: ./myapp/docker-compose.yml
|
||||
|
||||
${YELLOW}输出:${NC}
|
||||
在指定目录(默认 ./apps)下生成:
|
||||
<app-name>/<version>/
|
||||
├── data.yml # 应用元数据
|
||||
├── docker-compose.yml # 编排文件
|
||||
├── logo.png # 应用图标
|
||||
├── README.md # 中文简介
|
||||
└── README_en.md # 英文简介
|
||||
|
||||
${YELLOW}示例:${NC}
|
||||
$0 https://github.com/alist-org/alist
|
||||
$0 ./my-docker-compose.yml ./my-apps
|
||||
$0 "docker run -d --name nginx -p 80:80 nginx:latest"
|
||||
EOF
|
||||
}
|
||||
|
||||
# 日志函数
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# 检测输入类型
|
||||
detect_input_type() {
|
||||
local input="$1"
|
||||
|
||||
# GitHub 项目链接
|
||||
if [[ "$input" =~ ^https?://github\.com/[^/]+/[^/]+ ]]; then
|
||||
echo "github"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# docker-compose 文件链接
|
||||
if [[ "$input" =~ ^https?://.*\.ya?ml$ ]] || [[ "$input" =~ docker-compose ]]; then
|
||||
echo "compose_url"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# docker run 命令
|
||||
if [[ "$input" =~ ^docker\ run ]] || [[ "$input" =~ ^docker\ run$ ]]; then
|
||||
echo "docker_run"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 本地文件
|
||||
if [[ -f "$input" ]]; then
|
||||
if [[ "$input" =~ \.ya?ml$ ]]; then
|
||||
echo "compose_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "unknown"
|
||||
}
|
||||
|
||||
# 从 GitHub 项目提取信息
|
||||
extract_from_github() {
|
||||
local github_url="$1"
|
||||
local api_url temp_dir
|
||||
|
||||
log_info "正在从 GitHub 项目提取信息: $github_url"
|
||||
|
||||
# 转换为 API URL
|
||||
# https://github.com/owner/repo -> https://api.github.com/repos/owner/repo
|
||||
api_url="${github_url/github\.com/api.github.com\/repos}"
|
||||
|
||||
# 获取项目信息
|
||||
local repo_info
|
||||
repo_info=$(curl -s "$api_url" 2>/dev/null || echo "{}")
|
||||
|
||||
# 提取字段
|
||||
APP_NAME=$(echo "$repo_info" | jq -r '.name // empty')
|
||||
APP_KEY=$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')
|
||||
DESCRIPTION=$(echo "$repo_info" | jq -r '.description // empty')
|
||||
GITHUB="$github_url"
|
||||
WEBSITE=$(echo "$repo_info" | jq -r '.homepage // empty')
|
||||
if [[ -z "$WEBSITE" ]]; then
|
||||
WEBSITE="$github_url"
|
||||
fi
|
||||
|
||||
# 尝试查找 docker-compose.yml
|
||||
local owner_repo
|
||||
owner_repo=$(echo "$github_url" | sed -E 's|https?://github.com/||')
|
||||
COMPOSE_URL="https://raw.githubusercontent.com/${owner_repo}/main/docker-compose.yml"
|
||||
|
||||
log_info "应用名称: $APP_NAME"
|
||||
log_info "描述: $DESCRIPTION"
|
||||
}
|
||||
|
||||
# 从 docker-compose 提取信息
|
||||
extract_from_compose() {
|
||||
local compose_content="$1"
|
||||
|
||||
log_info "正在解析 docker-compose 内容"
|
||||
|
||||
# 提取服务名
|
||||
SERVICE_NAME=$(echo "$compose_content" | yq '.services | keys | .[0]' 2>/dev/null || echo "")
|
||||
|
||||
# 提取镜像
|
||||
local image
|
||||
image=$(echo "$compose_content" | yq ".services.${SERVICE_NAME}.image" 2>/dev/null || echo "")
|
||||
|
||||
# 解析镜像名和标签
|
||||
if [[ "$image" =~ ^([^:]+):(.+)$ ]]; then
|
||||
IMAGE="${BASH_REMATCH[1]}"
|
||||
TAG="${BASH_REMATCH[2]}"
|
||||
else
|
||||
IMAGE="$image"
|
||||
TAG="latest"
|
||||
fi
|
||||
|
||||
# 提取端口
|
||||
PORT=$(echo "$compose_content" | yq ".services.${SERVICE_NAME}.ports[0]" 2>/dev/null | grep -oE '[0-9]+$' || echo "8080")
|
||||
|
||||
log_info "服务名: $SERVICE_NAME"
|
||||
log_info "镜像: $IMAGE:$TAG"
|
||||
log_info "端口: $PORT"
|
||||
}
|
||||
|
||||
# 从 docker run 命令提取信息
|
||||
extract_from_docker_run() {
|
||||
local cmd="$1"
|
||||
|
||||
log_info "正在解析 docker run 命令"
|
||||
|
||||
# 提取镜像
|
||||
IMAGE=$(echo "$cmd" | grep -oE '[^ ]+:[^ ]+$' | cut -d':' -f1 || echo "")
|
||||
TAG=$(echo "$cmd" | grep -oE '[^ ]+:[^ ]+$' | cut -d':' -f2 || echo "latest")
|
||||
|
||||
# 提取容器名
|
||||
SERVICE_NAME=$(echo "$cmd" | grep -oE '\-\-name[= ]+[^ ]+' | awk '{print $NF}' || echo "app")
|
||||
|
||||
# 提取端口
|
||||
PORT=$(echo "$cmd" | grep -oE '\-p[= ]+[0-9]+:[0-9]+' | grep -oE '[0-9]+$' || echo "8080")
|
||||
|
||||
APP_NAME="$SERVICE_NAME"
|
||||
APP_KEY=$(echo "$SERVICE_NAME" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
log_info "应用名称: $APP_NAME"
|
||||
log_info "镜像: $IMAGE:$TAG"
|
||||
log_info "端口: $PORT"
|
||||
}
|
||||
|
||||
# 生成 data.yml
|
||||
generate_data_yml() {
|
||||
local output_file="$1"
|
||||
|
||||
log_info "生成 data.yml: $output_file"
|
||||
|
||||
cat > "$output_file" << EOF
|
||||
name: ${APP_NAME:-MyApp}
|
||||
tags:
|
||||
- 实用工具
|
||||
- 容器
|
||||
title: ${APP_NAME:-MyApp} - 容器应用
|
||||
description: ${DESCRIPTION:-自动生成的应用配置}
|
||||
additionalProperties:
|
||||
key: ${APP_KEY:-myapp}
|
||||
name: ${APP_NAME:-MyApp}
|
||||
tags:
|
||||
- Tool
|
||||
- Container
|
||||
shortDescZh: ${DESCRIPTION:-自动生成的应用配置}
|
||||
shortDescEn: ${DESCRIPTION:-Auto-generated application configuration}
|
||||
description:
|
||||
en: ${DESCRIPTION:-Auto-generated application configuration}
|
||||
ja: ${DESCRIPTION:-Auto-generated application configuration}
|
||||
ms: ${DESCRIPTION:-Auto-generated application configuration}
|
||||
pt-br: ${DESCRIPTION:-Auto-generated application configuration}
|
||||
ru: ${DESCRIPTION:-Auto-generated application configuration}
|
||||
ko: ${DESCRIPTION:-Auto-generated application configuration}
|
||||
zh-Hant: ${DESCRIPTION:-Auto-generated application configuration}
|
||||
zh: ${DESCRIPTION:-自动生成的应用配置}
|
||||
type: website
|
||||
crossVersionUpdate: true
|
||||
limit: 0
|
||||
recommend: 50
|
||||
website: ${WEBSITE:-https://example.com}
|
||||
github: ${GITHUB:-https://github.com}
|
||||
document: ${GITHUB:-https://github.com}
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
EOF
|
||||
}
|
||||
|
||||
# 生成 docker-compose.yml
|
||||
generate_docker_compose() {
|
||||
local output_file="$1"
|
||||
|
||||
log_info "生成 docker-compose.yml: $output_file"
|
||||
|
||||
cat > "$output_file" << EOF
|
||||
services:
|
||||
${SERVICE_NAME:-app}:
|
||||
container_name: \${CONTAINER_NAME}
|
||||
restart: always
|
||||
networks:
|
||||
- 1panel-network
|
||||
ports:
|
||||
- "\${PANEL_APP_PORT_HTTP}:${PORT:-8080}"
|
||||
volumes:
|
||||
- ./data/data:/app/data
|
||||
environment:
|
||||
- PUID=0
|
||||
- PGID=0
|
||||
- UMASK=022
|
||||
image: ${IMAGE:-app}:${TAG:-latest}
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
EOF
|
||||
}
|
||||
|
||||
# 生成 README
|
||||
generate_readme() {
|
||||
local output_dir="$1"
|
||||
|
||||
log_info "生成 README 文件"
|
||||
|
||||
cat > "${output_dir}/README.md" << EOF
|
||||
# ${APP_NAME:-MyApp}
|
||||
|
||||
## 简介
|
||||
|
||||
${DESCRIPTION:-这是一个自动生成的应用配置。}
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. 在 1Panel 应用商店中安装此应用
|
||||
2. 配置相关参数
|
||||
3. 启动应用
|
||||
|
||||
## 更多信息
|
||||
|
||||
- 官网: ${WEBSITE:-https://example.com}
|
||||
- GitHub: ${GITHUB:-https://github.com}
|
||||
- 文档: ${GITHUB:-https://github.com}
|
||||
EOF
|
||||
|
||||
cat > "${output_dir}/README_en.md" << EOF
|
||||
# ${APP_NAME:-MyApp}
|
||||
|
||||
## Introduction
|
||||
|
||||
${DESCRIPTION:-This is an auto-generated application configuration.}
|
||||
|
||||
## Usage
|
||||
|
||||
1. Install this app from 1Panel App Store
|
||||
2. Configure parameters
|
||||
3. Start the application
|
||||
|
||||
## More Information
|
||||
|
||||
- Website: ${WEBSITE:-https://example.com}
|
||||
- GitHub: ${GITHUB:-https://github.com}
|
||||
- Documentation: ${GITHUB:-https://github.com}
|
||||
EOF
|
||||
}
|
||||
|
||||
# 下载图标
|
||||
download_icon() {
|
||||
local app_name="$1"
|
||||
local output_file="$2"
|
||||
local icon_name
|
||||
|
||||
icon_name=$(echo "$app_name" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
log_info "尝试下载图标: $app_name"
|
||||
|
||||
# 尝试各个图标源
|
||||
for source in "${ICON_SOURCES[@]}"; do
|
||||
local icon_url="${source}/${icon_name}.png"
|
||||
if curl -sSL -o "$output_file" "$icon_url" 2>/dev/null && [[ -s "$output_file" ]]; then
|
||||
log_info "图标下载成功: $icon_url"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
log_warn "未找到图标,使用占位符"
|
||||
# 创建简单的占位图标(1x1 像素 PNG)
|
||||
echo -n "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" | base64 -d > "$output_file"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local input="$1"
|
||||
local input_type
|
||||
|
||||
# 参数检查
|
||||
if [[ -z "$input" ]] || [[ "$input" == "-h" ]] || [[ "$input" == "--help" ]]; then
|
||||
print_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_info "开始处理: $input"
|
||||
|
||||
# 检测输入类型
|
||||
input_type=$(detect_input_type "$input")
|
||||
log_info "检测到输入类型: $input_type"
|
||||
|
||||
# 根据类型提取信息
|
||||
case "$input_type" in
|
||||
github)
|
||||
extract_from_github "$input"
|
||||
COMPOSE_CONTENT=$(curl -s "$COMPOSE_URL" 2>/dev/null || echo "")
|
||||
if [[ -n "$COMPOSE_CONTENT" ]]; then
|
||||
extract_from_compose "$COMPOSE_CONTENT"
|
||||
fi
|
||||
;;
|
||||
compose_url)
|
||||
COMPOSE_CONTENT=$(curl -s "$input" 2>/dev/null)
|
||||
extract_from_compose "$COMPOSE_CONTENT"
|
||||
APP_NAME="$SERVICE_NAME"
|
||||
APP_KEY=$(echo "$SERVICE_NAME" | tr '[:upper:]' '[:lower:]')
|
||||
;;
|
||||
compose_file)
|
||||
COMPOSE_CONTENT=$(cat "$input")
|
||||
extract_from_compose "$COMPOSE_CONTENT"
|
||||
APP_NAME="$SERVICE_NAME"
|
||||
APP_KEY=$(echo "$SERVICE_NAME" | tr '[:upper:]' '[:lower:]')
|
||||
;;
|
||||
docker_run)
|
||||
extract_from_docker_run "$input"
|
||||
;;
|
||||
*)
|
||||
log_error "无法识别的输入类型: $input"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# 创建输出目录
|
||||
local output_dir="${OUTPUT_BASE}/${APP_KEY}/${TAG:-latest}"
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
log_info "输出目录: $output_dir"
|
||||
|
||||
# 生成文件
|
||||
generate_data_yml "${output_dir}/data.yml"
|
||||
generate_docker_compose "${output_dir}/docker-compose.yml"
|
||||
generate_readme "$output_dir"
|
||||
download_icon "$APP_NAME" "${output_dir}/logo.png"
|
||||
|
||||
# 生成上级目录的 data.yml
|
||||
generate_data_yml "${OUTPUT_BASE}/${APP_KEY}/data.yml"
|
||||
cp "${output_dir}/logo.png" "${OUTPUT_BASE}/${APP_KEY}/logo.png" 2>/dev/null || true
|
||||
cp "${output_dir}/README.md" "${OUTPUT_BASE}/${APP_KEY}/README.md" 2>/dev/null || true
|
||||
cp "${output_dir}/README_en.md" "${OUTPUT_BASE}/${APP_KEY}/README_en.md" 2>/dev/null || true
|
||||
|
||||
# 输出结果
|
||||
echo ""
|
||||
log_info "${GREEN}✓ 生成完成!${NC}"
|
||||
echo ""
|
||||
echo "目录结构:"
|
||||
tree "${OUTPUT_BASE}/${APP_KEY}" 2>/dev/null || find "${OUTPUT_BASE}/${APP_KEY}" -type f
|
||||
echo ""
|
||||
log_info "下一步:"
|
||||
echo " 1. 检查生成的配置文件"
|
||||
echo " 2. 补充完善应用描述和标签"
|
||||
echo " 3. 替换 logo.png 为合适的应用图标"
|
||||
echo " 4. 测试 docker-compose.yml"
|
||||
echo " 5. 提交到应用商店仓库"
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
257
skills/scripts/validate-app.sh
Normal file
257
skills/scripts/validate-app.sh
Normal file
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# =============================================================================
|
||||
# 1Panel App Validator - 验证生成的配置是否符合规范
|
||||
# 用法:./validate-app.sh <app-directory>
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 计数器
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
# 打印帮助
|
||||
print_help() {
|
||||
cat << EOF
|
||||
${BLUE}1Panel App Validator${NC} - 验证应用配置是否符合规范
|
||||
|
||||
${YELLOW}用法:${NC}
|
||||
$0 <app-directory>
|
||||
|
||||
${YELLOW}检查项目:${NC}
|
||||
- 目录结构完整性
|
||||
- data.yml 格式正确性
|
||||
- docker-compose.yml 变量使用
|
||||
- logo.png 是否存在
|
||||
- README 文件是否存在
|
||||
|
||||
${YELLOW}示例:${NC}
|
||||
$0 ./apps/alist
|
||||
EOF
|
||||
}
|
||||
|
||||
# 日志函数
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; ((WARNINGS++)); }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; ((ERRORS++)); }
|
||||
|
||||
# 检查目录结构
|
||||
check_directory_structure() {
|
||||
local app_dir="$1"
|
||||
local app_name
|
||||
app_name=$(basename "$app_dir")
|
||||
|
||||
log_info "检查目录结构: $app_name"
|
||||
|
||||
# 检查必需文件
|
||||
if [[ ! -f "$app_dir/data.yml" ]]; then
|
||||
log_error "缺少顶层 data.yml"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$app_dir/logo.png" ]]; then
|
||||
log_error "缺少 logo.png"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$app_dir/README.md" ]]; then
|
||||
log_warn "缺少 README.md(推荐)"
|
||||
fi
|
||||
|
||||
# 查找版本目录
|
||||
local version_dirs
|
||||
version_dirs=$(find "$app_dir" -maxdepth 1 -type d -name "v*" -o -name "latest" -o -name "[0-9]*" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$version_dirs" ]]; then
|
||||
log_error "未找到版本目录(如 1.0.0/, v1.0.0/, latest/)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查每个版本目录
|
||||
for version_dir in $version_dirs; do
|
||||
log_info "检查版本目录: $(basename "$version_dir")"
|
||||
|
||||
if [[ ! -f "$version_dir/data.yml" ]]; then
|
||||
log_error "版本目录缺少 data.yml: $version_dir"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$version_dir/docker-compose.yml" ]]; then
|
||||
log_error "版本目录缺少 docker-compose.yml: $version_dir"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# 验证顶层 data.yml
|
||||
validate_top_data_yml() {
|
||||
local data_file="$1"
|
||||
|
||||
log_info "验证顶层 data.yml"
|
||||
|
||||
if [[ ! -f "$data_file" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查必需字段
|
||||
local required_fields=("name" "title" "description")
|
||||
for field in "${required_fields[@]}"; do
|
||||
if ! grep -q "^${field}:" "$data_file"; then
|
||||
log_error "data.yml 缺少必需字段: $field"
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查 additionalProperties
|
||||
if ! grep -q "additionalProperties:" "$data_file"; then
|
||||
log_error "data.yml 缺少 additionalProperties"
|
||||
fi
|
||||
|
||||
# 检查 key
|
||||
if ! grep -q "key:" "$data_file"; then
|
||||
log_error "data.yml 缺少 additionalProperties.key"
|
||||
fi
|
||||
|
||||
# 检查 tags
|
||||
if ! grep -q "tags:" "$data_file"; then
|
||||
log_warn "data.yml 缺少 tags(推荐)"
|
||||
fi
|
||||
|
||||
# 检查 architectures
|
||||
if ! grep -q "architectures:" "$data_file"; then
|
||||
log_warn "data.yml 缺少 architectures(推荐)"
|
||||
fi
|
||||
}
|
||||
|
||||
# 验证版本 data.yml
|
||||
validate_version_data_yml() {
|
||||
local data_file="$1"
|
||||
|
||||
log_info "验证版本 data.yml"
|
||||
|
||||
if [[ ! -f "$data_file" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查 formFields
|
||||
if ! grep -q "formFields:" "$data_file"; then
|
||||
log_warn "版本 data.yml 缺少 formFields(如果无可配置参数则忽略)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 检查每个参数是否有 envKey
|
||||
local param_count
|
||||
param_count=$(grep -c "envKey:" "$data_file" 2>/dev/null || echo "0")
|
||||
|
||||
if [[ "$param_count" -eq 0 ]]; then
|
||||
log_warn "formFields 中未找到 envKey 定义"
|
||||
else
|
||||
log_info "找到 $param_count 个可配置参数"
|
||||
fi
|
||||
}
|
||||
|
||||
# 验证 docker-compose.yml
|
||||
validate_docker_compose() {
|
||||
local compose_file="$1"
|
||||
|
||||
log_info "验证 docker-compose.yml"
|
||||
|
||||
if [[ ! -f "$compose_file" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查 container_name 使用变量
|
||||
if grep -q "container_name:" "$compose_file"; then
|
||||
if ! grep -q 'container_name:.*\${CONTAINER_NAME}' "$compose_file"; then
|
||||
log_error "container_name 必须使用 \${CONTAINER_NAME} 变量"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查 networks
|
||||
if ! grep -q "1panel-network:" "$compose_file"; then
|
||||
log_error "docker-compose.yml 缺少 1panel-network"
|
||||
fi
|
||||
|
||||
if ! grep -q "external: true" "$compose_file"; then
|
||||
log_error "1panel-network 必须设置为 external: true"
|
||||
fi
|
||||
|
||||
# 检查 restart
|
||||
if ! grep -q "restart: always" "$compose_file"; then
|
||||
log_warn "建议设置 restart: always"
|
||||
fi
|
||||
|
||||
# 检查 labels
|
||||
if ! grep -q 'createdBy: "Apps"' "$compose_file"; then
|
||||
log_warn "建议添加 labels: createdBy: \"Apps\""
|
||||
fi
|
||||
|
||||
# 检查端口映射使用变量
|
||||
if grep -q "ports:" "$compose_file"; then
|
||||
if ! grep -q 'PANEL_APP_PORT_' "$compose_file"; then
|
||||
log_warn "端口映射建议使用 PANEL_APP_PORT_* 变量"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查数据卷路径
|
||||
if grep -q "volumes:" "$compose_file"; then
|
||||
if grep -qE '^\s+- /[a-zA-Z]' "$compose_file"; then
|
||||
log_warn "检测到绝对路径的数据卷,建议使用 ./data/ 相对路径"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local app_dir="${1:-}"
|
||||
|
||||
# 参数检查
|
||||
if [[ -z "$app_dir" ]] || [[ "$app_dir" == "-h" ]] || [[ "$app_dir" == "--help" ]]; then
|
||||
print_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -d "$app_dir" ]]; then
|
||||
log_error "目录不存在: $app_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}=== 1Panel App Validator ===${NC}"
|
||||
echo ""
|
||||
|
||||
# 执行检查
|
||||
check_directory_structure "$app_dir"
|
||||
validate_top_data_yml "$app_dir/data.yml"
|
||||
|
||||
# 查找并验证版本目录
|
||||
local version_dirs
|
||||
version_dirs=$(find "$app_dir" -maxdepth 1 -type d \( -name "v*" -o -name "latest" -o -name "[0-9]*" \) 2>/dev/null || echo "")
|
||||
|
||||
for version_dir in $version_dirs; do
|
||||
validate_version_data_yml "$version_dir/data.yml"
|
||||
validate_docker_compose "$version_dir/docker-compose.yml"
|
||||
done
|
||||
|
||||
# 输出结果
|
||||
echo ""
|
||||
echo -e "${BLUE}=== 验证结果 ===${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ $ERRORS -eq 0 ]] && [[ $WARNINGS -eq 0 ]]; then
|
||||
echo -e "${GREEN}✓ 验证通过!未发现问题。${NC}"
|
||||
exit 0
|
||||
elif [[ $ERRORS -eq 0 ]]; then
|
||||
echo -e "${YELLOW}⚠ 验证通过,但有 $WARNINGS 个警告。${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}✗ 验证失败!发现 $ERRORS 个错误和 $WARNINGS 个警告。${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user