From 6c39c6013d015ec68917f3e8d349a777f34d80f9 Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:29:52 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0metapi=E5=88=B0?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/metapi/1.3.0/.env.sample | 8 ++ apps/metapi/1.3.0/data.yml | 118 ++++++++++++++++++++++++ apps/metapi/1.3.0/data/.gitkeep | 0 apps/metapi/1.3.0/docker-compose.yml | 18 ++++ apps/metapi/1.3.0/scripts/init.sh | 4 + apps/metapi/1.3.0/scripts/uninstall.sh | 2 + apps/metapi/1.3.0/scripts/upgrade.sh | 4 + apps/metapi/README.md | 57 ++++++++++++ apps/metapi/data.yml | 30 ++++++ apps/metapi/latest/.env.sample | 8 ++ apps/metapi/latest/data.yml | 118 ++++++++++++++++++++++++ apps/metapi/latest/data/.gitkeep | 0 apps/metapi/latest/docker-compose.yml | 18 ++++ apps/metapi/latest/scripts/init.sh | 4 + apps/metapi/latest/scripts/uninstall.sh | 2 + apps/metapi/latest/scripts/upgrade.sh | 4 + apps/metapi/logo.png | Bin 0 -> 8459 bytes 17 files changed, 395 insertions(+) create mode 100644 apps/metapi/1.3.0/.env.sample create mode 100644 apps/metapi/1.3.0/data.yml create mode 100644 apps/metapi/1.3.0/data/.gitkeep create mode 100644 apps/metapi/1.3.0/docker-compose.yml create mode 100755 apps/metapi/1.3.0/scripts/init.sh create mode 100755 apps/metapi/1.3.0/scripts/uninstall.sh create mode 100755 apps/metapi/1.3.0/scripts/upgrade.sh create mode 100644 apps/metapi/README.md create mode 100644 apps/metapi/data.yml create mode 100644 apps/metapi/latest/.env.sample create mode 100644 apps/metapi/latest/data.yml create mode 100644 apps/metapi/latest/data/.gitkeep create mode 100644 apps/metapi/latest/docker-compose.yml create mode 100755 apps/metapi/latest/scripts/init.sh create mode 100755 apps/metapi/latest/scripts/uninstall.sh create mode 100755 apps/metapi/latest/scripts/upgrade.sh create mode 100755 apps/metapi/logo.png diff --git a/apps/metapi/1.3.0/.env.sample b/apps/metapi/1.3.0/.env.sample new file mode 100644 index 000000000..5a3d56f0a --- /dev/null +++ b/apps/metapi/1.3.0/.env.sample @@ -0,0 +1,8 @@ +APP_DATA_DIR="./data" +AUTH_TOKEN="" +BALANCE_REFRESH_CRON="0 * * * *" +CHECKIN_CRON="0 8 * * *" +CONTAINER_NAME="metapi" +PANEL_APP_PORT_HTTP=4000 +PROXY_TOKEN="" +TZ="Asia/Shanghai" diff --git a/apps/metapi/1.3.0/data.yml b/apps/metapi/1.3.0/data.yml new file mode 100644 index 000000000..bc64247e0 --- /dev/null +++ b/apps/metapi/1.3.0/data.yml @@ -0,0 +1,118 @@ +additionalProperties: + formFields: + - default: 4000 + envKey: PANEL_APP_PORT_HTTP + edit: true + labelEn: Port + labelZh: 端口 + label: + en: Port + zh: 端口 + zh-Hant: 埠 + ja: ポート + ko: 포트 + ru: Порт + ms: Port + pt-br: Porta + required: true + rule: paramPort + type: number + - default: ./data + envKey: APP_DATA_DIR + edit: true + labelEn: Data directory + labelZh: 数据目录 + label: + en: Data directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori data + pt-br: Diretório de dados + required: true + rule: paramCommon + type: text + - default: "" + envKey: AUTH_TOKEN + edit: true + labelEn: Admin token + labelZh: 管理员令牌 + label: + en: Admin token + zh: 管理员令牌 + zh-Hant: 管理員令牌 + ja: 管理者トークン + ko: 관리자 토큰 + ru: Администраторский токен + ms: Token pentadbir + pt-br: Token de administrador + required: true + type: password + - default: "" + envKey: PROXY_TOKEN + edit: true + labelEn: Proxy token + labelZh: 代理令牌 + label: + en: Proxy token + zh: 代理令牌 + zh-Hant: 代理令牌 + ja: プロキシトークン + ko: 프록시 토큰 + ru: Прокси‑токен + ms: Token proksi + pt-br: Token de proxy + required: true + type: password + - default: "0 8 * * *" + envKey: CHECKIN_CRON + edit: true + labelEn: Check-in cron + labelZh: 签到 Cron + label: + en: Check-in cron + zh: 签到 Cron + zh-Hant: 簽到 Cron + ja: チェックイン Cron + ko: 체크인 Cron + ru: Cron для签到 + ms: Cron daftar masuk + pt-br: Cron de check-in + required: false + rule: paramCommon + type: text + - default: "0 * * * *" + envKey: BALANCE_REFRESH_CRON + edit: true + labelEn: Balance refresh cron + labelZh: 余额刷新 Cron + label: + en: Balance refresh cron + zh: 余额刷新 Cron + zh-Hant: 餘額刷新 Cron + ja: 残高更新 Cron + ko: 잔액 새로고침 Cron + ru: Cron обновления баланса + ms: Cron segar semula baki + pt-br: Cron de atualização de saldo + required: false + rule: paramCommon + type: text + - default: Asia/Shanghai + envKey: TZ + edit: true + labelEn: Timezone + labelZh: 时区 + label: + en: Timezone + zh: 时区 + zh-Hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon waktu + pt-br: Fuso horário + required: true + type: text diff --git a/apps/metapi/1.3.0/data/.gitkeep b/apps/metapi/1.3.0/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/metapi/1.3.0/docker-compose.yml b/apps/metapi/1.3.0/docker-compose.yml new file mode 100644 index 000000000..8c7e89b58 --- /dev/null +++ b/apps/metapi/1.3.0/docker-compose.yml @@ -0,0 +1,18 @@ +services: + metapi: + image: "1467078763/metapi:latest" + ports: + - "${PANEL_APP_PORT_HTTP}:4000" + environment: + - "AUTH_TOKEN=${AUTH_TOKEN:?AUTH_TOKEN is required}" + - "PROXY_TOKEN=${PROXY_TOKEN:?PROXY_TOKEN is required}" + - "CHECKIN_CRON=${CHECKIN_CRON:-0 8 * * *}" + - "BALANCE_REFRESH_CRON=${BALANCE_REFRESH_CRON:-0 * * * *}" + - "PORT=4000" + - "DATA_DIR=/app/data" + - "TZ=${TZ}" + volumes: + - "${APP_DATA_DIR}:/app/data" + restart: unless-stopped + labels: + createdBy: "Apps" diff --git a/apps/metapi/1.3.0/scripts/init.sh b/apps/metapi/1.3.0/scripts/init.sh new file mode 100755 index 000000000..9d715d682 --- /dev/null +++ b/apps/metapi/1.3.0/scripts/init.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail + +mkdir -p "${APP_DATA_DIR:-./data}" diff --git a/apps/metapi/1.3.0/scripts/uninstall.sh b/apps/metapi/1.3.0/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/metapi/1.3.0/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/metapi/1.3.0/scripts/upgrade.sh b/apps/metapi/1.3.0/scripts/upgrade.sh new file mode 100755 index 000000000..eb7cd4346 --- /dev/null +++ b/apps/metapi/1.3.0/scripts/upgrade.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +# Reserved for future migrations. +exit 0 diff --git a/apps/metapi/README.md b/apps/metapi/README.md new file mode 100644 index 000000000..1ab6a3b3b --- /dev/null +++ b/apps/metapi/README.md @@ -0,0 +1,57 @@ +# Metapi + +Metapi 是一个面向多 AI 中转站聚合场景的统一网关,适合在 1Panel 中部署为集中入口,用于统一转发、令牌管理与定时任务调度。 + +## 应用简介 + +该应用将多个 AI 中转服务的访问能力收敛到一个统一网关内,方便在单一入口下完成鉴权、代理调用与日常维护。当前 1Panel 适配版本提供开箱即用的本地应用结构,部署后可直接通过 Web 端口对外提供服务。 + +## 功能亮点 + +- 聚合多个 AI 中转站,统一对外暴露访问入口 +- 支持管理员令牌与代理令牌配置,方便分权管理 +- 支持签到任务与余额刷新任务的 Cron 调度 +- 支持数据目录持久化,便于保留运行数据与后续迁移 +- 配置项集中在安装表单中维护,适合面板化运维 + +## 安装说明 + +安装时建议至少确认以下参数: + +- 访问端口 +- 数据目录 +- `AUTH_TOKEN` 管理员令牌 +- `PROXY_TOKEN` 代理令牌 +- `CHECKIN_CRON` 自动签到计划 +- `BALANCE_REFRESH_CRON` 余额刷新计划 +- 时区配置 + +其中两类令牌都属于关键安全参数,建议直接使用高强度随机值。 + +## 默认配置 + +- 访问端口:`4000` +- 容器内部端口:`4000` +- 默认数据目录:`./data` +- 容器内数据目录:`/app/data` +- 默认签到 Cron:`0 8 * * *` +- 默认余额刷新 Cron:`0 * * * *` +- 默认时区:`Asia/Shanghai` + +## 使用建议 + +- 首次安装时优先更换为高强度 `AUTH_TOKEN` 与 `PROXY_TOKEN` +- 若服务器已有统一反向代理,可将 Metapi 作为内部网关服务接入 +- Cron 任务建议按实际中转站频率与额度策略调整,避免过于激进 +- 数据目录建议放到稳定存储位置,便于备份与故障恢复 + +## 安装后检查 + +1. 打开 `http://<服务器IP>:<访问端口>` 确认服务已启动。 +2. 检查令牌配置是否生效。 +3. 检查数据目录是否已正确挂载。 +4. 观察签到与余额刷新任务是否按预期执行。 + +## 适配说明 + +当前目录为可直接导入 1Panel 的本地应用包,已按商店应用结构整理,重点保留安装表单、数据挂载和运行参数的面板化配置体验。 diff --git a/apps/metapi/data.yml b/apps/metapi/data.yml new file mode 100644 index 000000000..9b04ad943 --- /dev/null +++ b/apps/metapi/data.yml @@ -0,0 +1,30 @@ +name: Metapi +tags: + - AI +title: 聚合多个 AI 中转站的统一网关 +description: 聚合多个 AI 中转站的统一网关 +additionalProperties: + key: metapi + name: Metapi + tags: + - AI + shortDescZh: 聚合多个 AI 中转站的统一网关 + shortDescEn: Unified gateway aggregating multiple AI relay services + description: + en: Unified gateway aggregating multiple AI relay services + zh: 聚合多个 AI 中转站的统一网关 + zh-Hant: 聚合多個 AI 中轉站的統一網關 + ja: 複数のAI中継サービスを統合する統一ゲートウェイ + ko: 여러 AI 중계 서비스를 통합하는 통합 게이트웨이 + ru: Единый шлюз, объединяющий несколько AI‑ретрансляторов + ms: Gerbang bersatu yang menggabungkan pelbagai relay AI + pt-br: Gateway unificado que agrega múltiplos relay de IA + type: tool + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://metapi.cita777.me + github: https://github.com/cita-777/metapi + document: https://metapi.cita777.me + architectures: + - amd64 diff --git a/apps/metapi/latest/.env.sample b/apps/metapi/latest/.env.sample new file mode 100644 index 000000000..5a3d56f0a --- /dev/null +++ b/apps/metapi/latest/.env.sample @@ -0,0 +1,8 @@ +APP_DATA_DIR="./data" +AUTH_TOKEN="" +BALANCE_REFRESH_CRON="0 * * * *" +CHECKIN_CRON="0 8 * * *" +CONTAINER_NAME="metapi" +PANEL_APP_PORT_HTTP=4000 +PROXY_TOKEN="" +TZ="Asia/Shanghai" diff --git a/apps/metapi/latest/data.yml b/apps/metapi/latest/data.yml new file mode 100644 index 000000000..bc64247e0 --- /dev/null +++ b/apps/metapi/latest/data.yml @@ -0,0 +1,118 @@ +additionalProperties: + formFields: + - default: 4000 + envKey: PANEL_APP_PORT_HTTP + edit: true + labelEn: Port + labelZh: 端口 + label: + en: Port + zh: 端口 + zh-Hant: 埠 + ja: ポート + ko: 포트 + ru: Порт + ms: Port + pt-br: Porta + required: true + rule: paramPort + type: number + - default: ./data + envKey: APP_DATA_DIR + edit: true + labelEn: Data directory + labelZh: 数据目录 + label: + en: Data directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori data + pt-br: Diretório de dados + required: true + rule: paramCommon + type: text + - default: "" + envKey: AUTH_TOKEN + edit: true + labelEn: Admin token + labelZh: 管理员令牌 + label: + en: Admin token + zh: 管理员令牌 + zh-Hant: 管理員令牌 + ja: 管理者トークン + ko: 관리자 토큰 + ru: Администраторский токен + ms: Token pentadbir + pt-br: Token de administrador + required: true + type: password + - default: "" + envKey: PROXY_TOKEN + edit: true + labelEn: Proxy token + labelZh: 代理令牌 + label: + en: Proxy token + zh: 代理令牌 + zh-Hant: 代理令牌 + ja: プロキシトークン + ko: 프록시 토큰 + ru: Прокси‑токен + ms: Token proksi + pt-br: Token de proxy + required: true + type: password + - default: "0 8 * * *" + envKey: CHECKIN_CRON + edit: true + labelEn: Check-in cron + labelZh: 签到 Cron + label: + en: Check-in cron + zh: 签到 Cron + zh-Hant: 簽到 Cron + ja: チェックイン Cron + ko: 체크인 Cron + ru: Cron для签到 + ms: Cron daftar masuk + pt-br: Cron de check-in + required: false + rule: paramCommon + type: text + - default: "0 * * * *" + envKey: BALANCE_REFRESH_CRON + edit: true + labelEn: Balance refresh cron + labelZh: 余额刷新 Cron + label: + en: Balance refresh cron + zh: 余额刷新 Cron + zh-Hant: 餘額刷新 Cron + ja: 残高更新 Cron + ko: 잔액 새로고침 Cron + ru: Cron обновления баланса + ms: Cron segar semula baki + pt-br: Cron de atualização de saldo + required: false + rule: paramCommon + type: text + - default: Asia/Shanghai + envKey: TZ + edit: true + labelEn: Timezone + labelZh: 时区 + label: + en: Timezone + zh: 时区 + zh-Hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon waktu + pt-br: Fuso horário + required: true + type: text diff --git a/apps/metapi/latest/data/.gitkeep b/apps/metapi/latest/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/metapi/latest/docker-compose.yml b/apps/metapi/latest/docker-compose.yml new file mode 100644 index 000000000..8c7e89b58 --- /dev/null +++ b/apps/metapi/latest/docker-compose.yml @@ -0,0 +1,18 @@ +services: + metapi: + image: "1467078763/metapi:latest" + ports: + - "${PANEL_APP_PORT_HTTP}:4000" + environment: + - "AUTH_TOKEN=${AUTH_TOKEN:?AUTH_TOKEN is required}" + - "PROXY_TOKEN=${PROXY_TOKEN:?PROXY_TOKEN is required}" + - "CHECKIN_CRON=${CHECKIN_CRON:-0 8 * * *}" + - "BALANCE_REFRESH_CRON=${BALANCE_REFRESH_CRON:-0 * * * *}" + - "PORT=4000" + - "DATA_DIR=/app/data" + - "TZ=${TZ}" + volumes: + - "${APP_DATA_DIR}:/app/data" + restart: unless-stopped + labels: + createdBy: "Apps" diff --git a/apps/metapi/latest/scripts/init.sh b/apps/metapi/latest/scripts/init.sh new file mode 100755 index 000000000..9d715d682 --- /dev/null +++ b/apps/metapi/latest/scripts/init.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail + +mkdir -p "${APP_DATA_DIR:-./data}" diff --git a/apps/metapi/latest/scripts/uninstall.sh b/apps/metapi/latest/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/metapi/latest/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/metapi/latest/scripts/upgrade.sh b/apps/metapi/latest/scripts/upgrade.sh new file mode 100755 index 000000000..eb7cd4346 --- /dev/null +++ b/apps/metapi/latest/scripts/upgrade.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +# Reserved for future migrations. +exit 0 diff --git a/apps/metapi/logo.png b/apps/metapi/logo.png new file mode 100755 index 0000000000000000000000000000000000000000..3685399ed91871e1215d2e0df1dfeb1e4d50b939 GIT binary patch literal 8459 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 Date: Tue, 14 Apr 2026 23:29:52 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0sub2api=E5=88=B0?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sub2api/0.1.112/.env.sample | 21 ++ apps/sub2api/0.1.112/data.yml | 350 ++++++++++++++++++++++ apps/sub2api/0.1.112/data/.gitkeep | 0 apps/sub2api/0.1.112/docker-compose.yml | 46 +++ apps/sub2api/0.1.112/scripts/init.sh | 6 + apps/sub2api/0.1.112/scripts/uninstall.sh | 6 + apps/sub2api/0.1.112/scripts/upgrade.sh | 6 + apps/sub2api/README.md | 81 +++++ apps/sub2api/data.yml | 30 ++ apps/sub2api/latest/.env.sample | 21 ++ apps/sub2api/latest/data.yml | 350 ++++++++++++++++++++++ apps/sub2api/latest/data/.gitkeep | 0 apps/sub2api/latest/docker-compose.yml | 46 +++ apps/sub2api/latest/scripts/init.sh | 6 + apps/sub2api/latest/scripts/uninstall.sh | 6 + apps/sub2api/latest/scripts/upgrade.sh | 6 + apps/sub2api/logo.png | Bin 0 -> 6603 bytes 17 files changed, 981 insertions(+) create mode 100644 apps/sub2api/0.1.112/.env.sample create mode 100644 apps/sub2api/0.1.112/data.yml create mode 100644 apps/sub2api/0.1.112/data/.gitkeep create mode 100644 apps/sub2api/0.1.112/docker-compose.yml create mode 100755 apps/sub2api/0.1.112/scripts/init.sh create mode 100755 apps/sub2api/0.1.112/scripts/uninstall.sh create mode 100755 apps/sub2api/0.1.112/scripts/upgrade.sh create mode 100644 apps/sub2api/README.md create mode 100644 apps/sub2api/data.yml create mode 100644 apps/sub2api/latest/.env.sample create mode 100644 apps/sub2api/latest/data.yml create mode 100644 apps/sub2api/latest/data/.gitkeep create mode 100644 apps/sub2api/latest/docker-compose.yml create mode 100755 apps/sub2api/latest/scripts/init.sh create mode 100755 apps/sub2api/latest/scripts/uninstall.sh create mode 100755 apps/sub2api/latest/scripts/upgrade.sh create mode 100644 apps/sub2api/logo.png diff --git a/apps/sub2api/0.1.112/.env.sample b/apps/sub2api/0.1.112/.env.sample new file mode 100644 index 000000000..88ba30f7b --- /dev/null +++ b/apps/sub2api/0.1.112/.env.sample @@ -0,0 +1,21 @@ +CONTAINER_NAME="sub2api" +PANEL_APP_PORT_HTTP="8080" +TZ="Asia/Shanghai" +SERVER_MODE="release" +RUN_MODE="standard" +PANEL_DB_TYPE="postgresql" +PANEL_DB_HOST="" +PANEL_DB_NAME="sub2api" +PANEL_DB_USER="sub2api" +PANEL_DB_PORT="5432" +PANEL_DB_USER_PASSWORD="" +REDIS_HOST="" +REDIS_PORT="6379" +PANEL_REDIS_ROOT_PASSWORD="" +REDIS_DB="0" +ADMIN_EMAIL="admin@sub2api.local" +ADMIN_PASSWORD="" +JWT_SECRET="" +TOTP_ENCRYPTION_KEY="" +UPDATE_PROXY_URL="" +APP_DATA_DIR_1="./data" diff --git a/apps/sub2api/0.1.112/data.yml b/apps/sub2api/0.1.112/data.yml new file mode 100644 index 000000000..e3dfe8d8f --- /dev/null +++ b/apps/sub2api/0.1.112/data.yml @@ -0,0 +1,350 @@ +additionalProperties: + formFields: + - child: + default: "" + envKey: PANEL_DB_HOST + labelEn: Database Service + labelZh: 数据库服务 + label: + en: Database Service + zh: 数据库服务 + zh-hant: 資料庫服務 + ja: データベースサービス + ko: 데이터베이스 서비스 + ru: Служба базы данных + ms: Perkhidmatan Pangkalan Data + pt-br: Serviço de Banco de Dados + required: true + type: service + default: postgresql + envKey: PANEL_DB_TYPE + labelEn: Database + labelZh: 数据库服务 + label: + en: Database + zh: 数据库服务 + zh-hant: 資料庫服務 + ja: データベース + ko: 데이터베이스 + ru: База данных + ms: Pangkalan Data + pt-br: Banco de dados + required: true + type: apps + values: + - label: PostgreSQL + value: postgresql + - default: sub2api + edit: true + envKey: PANEL_DB_NAME + labelEn: Database Name + labelZh: 数据库名 + label: + en: Database Name + zh: 数据库名 + zh-hant: 資料庫名稱 + ja: データベース名 + ko: 데이터베이스 이름 + ru: Имя базы данных + ms: Nama pangkalan data + pt-br: Nome do banco de dados + random: true + required: true + rule: paramCommon + type: text + - default: sub2api + edit: true + envKey: PANEL_DB_USER + labelEn: Database User + labelZh: 数据库用户 + label: + en: Database User + zh: 数据库用户 + zh-hant: 資料庫使用者 + ja: データベースユーザー + ko: 데이터베이스 사용자 + ru: Пользователь базы данных + ms: Nama pengguna pangkalan data + pt-br: Usuário do banco de dados + random: true + required: true + rule: paramCommon + type: text + - default: 5432 + edit: true + envKey: PANEL_DB_PORT + labelEn: Database Port + labelZh: 数据库端口 + label: + en: Database Port + zh: 数据库端口 + zh-hant: 資料庫連接埠 + ja: データベースポート + ko: 데이터베이스 포트 + ru: Порт базы данных + ms: Port pangkalan data + pt-br: Porta do banco de dados + required: true + rule: paramPort + type: number + - default: "" + edit: true + envKey: PANEL_DB_USER_PASSWORD + labelEn: Database Password + labelZh: 数据库用户密码 + label: + en: Database Password + zh: 数据库用户密码 + zh-hant: 資料庫使用者密碼 + ja: データベースパスワード + ko: 데이터베이스 비밀번호 + ru: Пароль базы данных + ms: Kata laluan pangkalan data + pt-br: Senha do banco de dados + random: true + required: true + rule: paramComplexity + type: password + - default: "" + edit: true + envKey: REDIS_HOST + key: redis + labelEn: Redis Service + labelZh: Redis服务 + label: + en: Redis Service + zh: Redis服务 + zh-hant: Redis 服務 + ja: Redis サービス + ko: Redis 서비스 + ru: Служба Redis + ms: Perkhidmatan Redis + pt-br: Serviço Redis + required: true + type: service + - default: 6379 + edit: true + envKey: REDIS_PORT + labelEn: Redis Service Port + labelZh: Redis服务端口 + label: + en: Redis Service Port + zh: Redis服务端口 + zh-hant: Redis 服務連接埠 + ja: Redis サービスポート + ko: Redis 서비스 포트 + ru: Порт службы Redis + ms: Port Perkhidmatan Redis + pt-br: Porta do serviço Redis + required: true + rule: paramPort + type: number + - default: "" + edit: true + envKey: PANEL_REDIS_ROOT_PASSWORD + labelEn: Redis Password + labelZh: Redis 密码 + label: + en: Redis Password + zh: Redis 密码 + zh-hant: Redis 密碼 + ja: Redis パスワード + ko: Redis 비밀번호 + ru: Пароль Redis + ms: Kata laluan Redis + pt-br: Senha do Redis + random: true + required: true + type: password + - default: 0 + edit: true + envKey: REDIS_DB + labelEn: Redis Database + labelZh: Redis 数据库 + label: + en: Redis Database + zh: Redis 数据库 + zh-hant: Redis 資料庫 + ja: Redis データベース + ko: Redis 데이터베이스 + ru: База данных Redis + ms: Pangkalan Data Redis + pt-br: Banco de dados Redis + required: true + type: number + - default: 8080 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Web Port + labelZh: Web 访问端口 + label: + en: Web Port + zh: Web 访问端口 + zh-hant: Web 訪問埠 + ja: Webポート + ko: 웹 포트 + ru: Веб-порт + ms: Port Web + pt-br: Porta Web + required: true + rule: paramPort + type: number + - default: Asia/Shanghai + edit: true + envKey: TZ + labelEn: Timezone + labelZh: 时区 + label: + en: Timezone + zh: 时区 + zh-hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon masa + pt-br: Fuso horário + required: true + type: text + - default: release + edit: true + envKey: SERVER_MODE + labelEn: Server Mode + labelZh: 服务模式 + label: + en: Server Mode + zh: 服务模式 + zh-hant: 服務模式 + ja: サーバーモード + ko: 서버 모드 + ru: Режим сервера + ms: Mod pelayan + pt-br: Modo do servidor + required: true + type: select + values: + - label: release + value: release + - label: debug + value: debug + - default: standard + edit: true + envKey: RUN_MODE + labelEn: Run Mode + labelZh: 运行模式 + label: + en: Run Mode + zh: 运行模式 + zh-hant: 運行模式 + ja: 実行モード + ko: 실행 모드 + ru: Режим работы + ms: Mod jalanan + pt-br: Modo de execução + required: true + type: select + values: + - label: standard + value: standard + - label: simple + value: simple + - default: admin@sub2api.local + edit: true + envKey: ADMIN_EMAIL + labelEn: Admin Email + labelZh: 管理员邮箱 + label: + en: Admin Email + zh: 管理员邮箱 + zh-hant: 管理員郵箱 + ja: 管理者メール + ko: 관리자 이메일 + ru: Email администратора + ms: E-mel pentadbir + pt-br: E-mail do administrador + required: true + type: text + - default: "" + edit: true + envKey: ADMIN_PASSWORD + labelEn: Admin Password + labelZh: 管理员密码 + label: + en: Admin Password + zh: 管理员密码 + zh-hant: 管理員密碼 + ja: 管理者パスワード + ko: 관리자 비밀번호 + ru: Пароль администратора + ms: Kata laluan pentadbir + pt-br: Senha do administrador + random: true + required: false + rule: paramComplexity + type: password + - default: "" + edit: true + envKey: JWT_SECRET + labelEn: JWT Secret + labelZh: JWT 密钥 + label: + en: JWT Secret + zh: JWT 密钥 + zh-hant: JWT 金鑰 + ja: JWT シークレット + ko: JWT 시크릿 + ru: JWT-секрет + ms: Rahsia JWT + pt-br: Segredo JWT + random: true + required: true + type: password + - default: "" + edit: true + envKey: TOTP_ENCRYPTION_KEY + labelEn: TOTP Encryption Key + labelZh: TOTP 加密密钥 + label: + en: TOTP Encryption Key + zh: TOTP 加密密钥 + zh-hant: TOTP 加密金鑰 + ja: TOTP 暗号化キー + ko: TOTP 암호화 키 + ru: Ключ шифрования TOTP + ms: Kunci penyulitan TOTP + pt-br: Chave de criptografia TOTP + random: true + required: true + type: password + - default: "" + edit: true + envKey: UPDATE_PROXY_URL + labelEn: Update Proxy URL + labelZh: 更新代理地址 + label: + en: Update Proxy URL + zh: 更新代理地址 + zh-hant: 更新代理地址 + ja: 更新プロキシ URL + ko: 업데이트 프록시 URL + ru: URL прокси для обновлений + ms: URL proksi kemas kini + pt-br: URL do proxy de atualização + required: false + rule: paramExtUrl + type: text + - default: ./data + edit: true + envKey: APP_DATA_DIR_1 + labelEn: App Data Directory + labelZh: 应用数据目录 + label: + en: App Data Directory + zh: 应用数据目录 + zh-hant: 應用資料目錄 + ja: アプリデータディレクトリ + ko: 앱 데이터 디렉터리 + ru: Каталог данных приложения + ms: Direktori data aplikasi + pt-br: Diretório de dados da aplicação + required: true + type: text diff --git a/apps/sub2api/0.1.112/data/.gitkeep b/apps/sub2api/0.1.112/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/sub2api/0.1.112/docker-compose.yml b/apps/sub2api/0.1.112/docker-compose.yml new file mode 100644 index 000000000..7034e131c --- /dev/null +++ b/apps/sub2api/0.1.112/docker-compose.yml @@ -0,0 +1,46 @@ +services: + sub2api: + image: "ghcr.io/wei-shaw/sub2api:0.1.112" + container_name: ${CONTAINER_NAME} + restart: always + networks: + - 1panel-network + environment: + - AUTO_SETUP=true + - SERVER_HOST=0.0.0.0 + - SERVER_PORT=8080 + - SERVER_MODE=${SERVER_MODE} + - RUN_MODE=${RUN_MODE} + - DATABASE_HOST=${PANEL_DB_HOST} + - DATABASE_PORT=${PANEL_DB_PORT} + - DATABASE_USER=${PANEL_DB_USER} + - DATABASE_PASSWORD=${PANEL_DB_USER_PASSWORD} + - DATABASE_DBNAME=${PANEL_DB_NAME} + - DATABASE_SSLMODE=disable + - REDIS_HOST=${REDIS_HOST} + - REDIS_PORT=${REDIS_PORT} + - REDIS_PASSWORD=${PANEL_REDIS_ROOT_PASSWORD} + - REDIS_DB=${REDIS_DB} + - ADMIN_EMAIL=${ADMIN_EMAIL} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-} + - JWT_SECRET=${JWT_SECRET} + - JWT_EXPIRE_HOUR=24 + - TOTP_ENCRYPTION_KEY=${TOTP_ENCRYPTION_KEY} + - TZ=${TZ} + - UPDATE_PROXY_URL=${UPDATE_PROXY_URL:-} + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + volumes: + - "${APP_DATA_DIR_1}:/app/data" + labels: + createdBy: "Apps" + healthcheck: + test: ["CMD", "wget", "-q", "-T", "5", "-O", "/dev/null", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + +networks: + 1panel-network: + external: true diff --git a/apps/sub2api/0.1.112/scripts/init.sh b/apps/sub2api/0.1.112/scripts/init.sh new file mode 100755 index 000000000..51bfe1764 --- /dev/null +++ b/apps/sub2api/0.1.112/scripts/init.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail + +# 当前适配使用 1Panel 数据库 / Redis 依赖注入。 +# 这里只初始化应用自身的数据目录。 +mkdir -p "${APP_DATA_DIR_1:-./data}" diff --git a/apps/sub2api/0.1.112/scripts/uninstall.sh b/apps/sub2api/0.1.112/scripts/uninstall.sh new file mode 100755 index 000000000..20a0bf525 --- /dev/null +++ b/apps/sub2api/0.1.112/scripts/uninstall.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail + +# 当前适配只卸载 Sub2API 容器本身。 +# 数据库与 Redis 为 1Panel 依赖应用,不在此处删除。 +docker-compose down --volumes diff --git a/apps/sub2api/0.1.112/scripts/upgrade.sh b/apps/sub2api/0.1.112/scripts/upgrade.sh new file mode 100755 index 000000000..622a93268 --- /dev/null +++ b/apps/sub2api/0.1.112/scripts/upgrade.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail + +# 升级时确保应用自身的数据目录存在。 +# 数据库与 Redis 数据由 1Panel 依赖应用管理。 +mkdir -p "${APP_DATA_DIR_1:-./data}" diff --git a/apps/sub2api/README.md b/apps/sub2api/README.md new file mode 100644 index 000000000..968378846 --- /dev/null +++ b/apps/sub2api/README.md @@ -0,0 +1,81 @@ +# Sub2API + +Sub2API 是一个面向订阅转换与统一接口访问场景的轻量服务,适合在 1Panel 中作为独立应用部署,为客户端或上游系统提供稳定的订阅处理入口。 + +## 应用简介 + +该应用提供 Web 访问入口,并支持自动初始化、管理员账户设置、日志级别控制、JWT/TOTP 安全配置,以及基于 PostgreSQL 与 Redis 的持久化与缓存能力。当前 1Panel 适配版本采用“应用本体 + 商店依赖”的方式交付,便于直接复用面板内已有的数据库与缓存服务。 + +## 功能亮点 + +- 支持订阅转换与 API 化访问,方便统一接入与后续集成 +- 支持 PostgreSQL + Redis 组合依赖,适合长期运行 +- 支持管理员邮箱、管理员密码、JWT 密钥、TOTP 加密密钥等初始化参数 +- 支持日志级别与安全 URL 白名单开关,便于按环境调整 +- 支持数据目录挂载,方便备份、迁移与持久化管理 + +## 持久化目录 + +适配默认使用相对目录持久化应用数据: + +- `./data` → `/app/data` + +其中 `/app/data` 会保存应用运行数据、自动生成的 `config.yaml`、日志以及部分缓存内容。上游入口脚本会在启动时确保该目录可写。数据库和 Redis 的数据由对应的 1Panel 依赖应用自行管理。 + +## 安装说明 + +安装时需要在 1Panel 表单中补齐以下关键信息: + +- PostgreSQL 服务实例 +- Redis 服务实例 +- 管理员邮箱 +- 访问端口 +- 数据目录 +- 安全参数:`JWT_SECRET`、`TOTP_ENCRYPTION_KEY`、`ADMIN_PASSWORD` + +其中数据库与缓存服务由 1Panel 商店依赖注入: + +- PostgreSQL:用于应用数据持久化 +- Redis:用于缓存与运行时状态 + +若面板中已经安装对应服务,直接在安装表单中选择实例与 Service 即可。 + +## 默认配置 + +- 访问端口:`8080` +- 容器内部端口:`8080` +- 默认数据目录:`./data` +- 容器内数据目录:`/app/data` +- 默认时区:`Asia/Shanghai` +- 默认日志级别:`info` + +## 使用建议 + +- 首次安装时补全管理员邮箱,建议同时设置管理员密码 +- 生产环境建议显式配置 `JWT_SECRET` 与 `TOTP_ENCRYPTION_KEY` +- 若启用 URL 安全白名单,请提前确认业务访问范围 +- 如需代理更新源,可按需填写 `UPDATE_PROXY_URL` + +## 安装后检查 + +1. 打开 `http://<服务器IP>:<访问端口>` 确认页面可访问。 +2. 检查数据库与 Redis 连接是否正常。 +3. 确认数据目录已正确挂载并具备写入权限。 +4. 若使用初始化管理员账户,确认可正常登录后台。 + +## 反向代理说明 + +如果你在 Nginx 后面反代 Sub2API,并且需要兼容 Codex CLI 等依赖下划线请求头的客户端,请在 Nginx 的 `http` 块中开启: + +```nginx +underscores_in_headers on; +``` + +这是上游 README 明确提到的要求,否则某些带下划线的头部可能会被 Nginx 丢弃,影响会话粘性与部分 CLI 场景。 + +## 备注 + +- 本适配默认启用 `AUTO_SETUP=true` +- 默认时区为 `Asia/Shanghai` +- 生命周期脚本只会初始化应用自身的 `./data` 目录,不会创建数据库或 Redis 的本地目录,因为这两者由 1Panel 依赖应用托管 +- 如需更细的运行参数,可参考 `latest/.env.sample` 和上游文档继续扩展 diff --git a/apps/sub2api/data.yml b/apps/sub2api/data.yml new file mode 100644 index 000000000..c9f9c018b --- /dev/null +++ b/apps/sub2api/data.yml @@ -0,0 +1,30 @@ +name: Sub2API +tags: + - AI +title: 提供 OpenAI 兼容接口的 AI 网关服务 +description: 提供 OpenAI 兼容接口的 AI 网关服务 +additionalProperties: + key: sub2api + name: Sub2API + tags: + - AI + shortDescZh: 提供 OpenAI 兼容接口的 AI 网关服务 + shortDescEn: AI gateway service with OpenAI-compatible APIs + description: + en: AI gateway service with OpenAI-compatible APIs + zh: 提供 OpenAI 兼容接口的 AI 网关服务 + zh-Hant: 提供 OpenAI 相容介面的 AI 閘道服務 + ja: OpenAI 互換 API を提供する AI ゲートウェイサービス + ko: OpenAI 호환 API를 제공하는 AI 게이트웨이 서비스 + ru: AI-шлюз с API, совместимыми с OpenAI + ms: Perkhidmatan gerbang AI dengan API serasi OpenAI + pt-br: Serviço de gateway de IA com APIs compatíveis com OpenAI + type: tool + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://github.com/Wei-Shaw/sub2api + github: https://github.com/Wei-Shaw/sub2api + document: https://github.com/Wei-Shaw/sub2api + architectures: + - amd64 diff --git a/apps/sub2api/latest/.env.sample b/apps/sub2api/latest/.env.sample new file mode 100644 index 000000000..88ba30f7b --- /dev/null +++ b/apps/sub2api/latest/.env.sample @@ -0,0 +1,21 @@ +CONTAINER_NAME="sub2api" +PANEL_APP_PORT_HTTP="8080" +TZ="Asia/Shanghai" +SERVER_MODE="release" +RUN_MODE="standard" +PANEL_DB_TYPE="postgresql" +PANEL_DB_HOST="" +PANEL_DB_NAME="sub2api" +PANEL_DB_USER="sub2api" +PANEL_DB_PORT="5432" +PANEL_DB_USER_PASSWORD="" +REDIS_HOST="" +REDIS_PORT="6379" +PANEL_REDIS_ROOT_PASSWORD="" +REDIS_DB="0" +ADMIN_EMAIL="admin@sub2api.local" +ADMIN_PASSWORD="" +JWT_SECRET="" +TOTP_ENCRYPTION_KEY="" +UPDATE_PROXY_URL="" +APP_DATA_DIR_1="./data" diff --git a/apps/sub2api/latest/data.yml b/apps/sub2api/latest/data.yml new file mode 100644 index 000000000..e3dfe8d8f --- /dev/null +++ b/apps/sub2api/latest/data.yml @@ -0,0 +1,350 @@ +additionalProperties: + formFields: + - child: + default: "" + envKey: PANEL_DB_HOST + labelEn: Database Service + labelZh: 数据库服务 + label: + en: Database Service + zh: 数据库服务 + zh-hant: 資料庫服務 + ja: データベースサービス + ko: 데이터베이스 서비스 + ru: Служба базы данных + ms: Perkhidmatan Pangkalan Data + pt-br: Serviço de Banco de Dados + required: true + type: service + default: postgresql + envKey: PANEL_DB_TYPE + labelEn: Database + labelZh: 数据库服务 + label: + en: Database + zh: 数据库服务 + zh-hant: 資料庫服務 + ja: データベース + ko: 데이터베이스 + ru: База данных + ms: Pangkalan Data + pt-br: Banco de dados + required: true + type: apps + values: + - label: PostgreSQL + value: postgresql + - default: sub2api + edit: true + envKey: PANEL_DB_NAME + labelEn: Database Name + labelZh: 数据库名 + label: + en: Database Name + zh: 数据库名 + zh-hant: 資料庫名稱 + ja: データベース名 + ko: 데이터베이스 이름 + ru: Имя базы данных + ms: Nama pangkalan data + pt-br: Nome do banco de dados + random: true + required: true + rule: paramCommon + type: text + - default: sub2api + edit: true + envKey: PANEL_DB_USER + labelEn: Database User + labelZh: 数据库用户 + label: + en: Database User + zh: 数据库用户 + zh-hant: 資料庫使用者 + ja: データベースユーザー + ko: 데이터베이스 사용자 + ru: Пользователь базы данных + ms: Nama pengguna pangkalan data + pt-br: Usuário do banco de dados + random: true + required: true + rule: paramCommon + type: text + - default: 5432 + edit: true + envKey: PANEL_DB_PORT + labelEn: Database Port + labelZh: 数据库端口 + label: + en: Database Port + zh: 数据库端口 + zh-hant: 資料庫連接埠 + ja: データベースポート + ko: 데이터베이스 포트 + ru: Порт базы данных + ms: Port pangkalan data + pt-br: Porta do banco de dados + required: true + rule: paramPort + type: number + - default: "" + edit: true + envKey: PANEL_DB_USER_PASSWORD + labelEn: Database Password + labelZh: 数据库用户密码 + label: + en: Database Password + zh: 数据库用户密码 + zh-hant: 資料庫使用者密碼 + ja: データベースパスワード + ko: 데이터베이스 비밀번호 + ru: Пароль базы данных + ms: Kata laluan pangkalan data + pt-br: Senha do banco de dados + random: true + required: true + rule: paramComplexity + type: password + - default: "" + edit: true + envKey: REDIS_HOST + key: redis + labelEn: Redis Service + labelZh: Redis服务 + label: + en: Redis Service + zh: Redis服务 + zh-hant: Redis 服務 + ja: Redis サービス + ko: Redis 서비스 + ru: Служба Redis + ms: Perkhidmatan Redis + pt-br: Serviço Redis + required: true + type: service + - default: 6379 + edit: true + envKey: REDIS_PORT + labelEn: Redis Service Port + labelZh: Redis服务端口 + label: + en: Redis Service Port + zh: Redis服务端口 + zh-hant: Redis 服務連接埠 + ja: Redis サービスポート + ko: Redis 서비스 포트 + ru: Порт службы Redis + ms: Port Perkhidmatan Redis + pt-br: Porta do serviço Redis + required: true + rule: paramPort + type: number + - default: "" + edit: true + envKey: PANEL_REDIS_ROOT_PASSWORD + labelEn: Redis Password + labelZh: Redis 密码 + label: + en: Redis Password + zh: Redis 密码 + zh-hant: Redis 密碼 + ja: Redis パスワード + ko: Redis 비밀번호 + ru: Пароль Redis + ms: Kata laluan Redis + pt-br: Senha do Redis + random: true + required: true + type: password + - default: 0 + edit: true + envKey: REDIS_DB + labelEn: Redis Database + labelZh: Redis 数据库 + label: + en: Redis Database + zh: Redis 数据库 + zh-hant: Redis 資料庫 + ja: Redis データベース + ko: Redis 데이터베이스 + ru: База данных Redis + ms: Pangkalan Data Redis + pt-br: Banco de dados Redis + required: true + type: number + - default: 8080 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Web Port + labelZh: Web 访问端口 + label: + en: Web Port + zh: Web 访问端口 + zh-hant: Web 訪問埠 + ja: Webポート + ko: 웹 포트 + ru: Веб-порт + ms: Port Web + pt-br: Porta Web + required: true + rule: paramPort + type: number + - default: Asia/Shanghai + edit: true + envKey: TZ + labelEn: Timezone + labelZh: 时区 + label: + en: Timezone + zh: 时区 + zh-hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon masa + pt-br: Fuso horário + required: true + type: text + - default: release + edit: true + envKey: SERVER_MODE + labelEn: Server Mode + labelZh: 服务模式 + label: + en: Server Mode + zh: 服务模式 + zh-hant: 服務模式 + ja: サーバーモード + ko: 서버 모드 + ru: Режим сервера + ms: Mod pelayan + pt-br: Modo do servidor + required: true + type: select + values: + - label: release + value: release + - label: debug + value: debug + - default: standard + edit: true + envKey: RUN_MODE + labelEn: Run Mode + labelZh: 运行模式 + label: + en: Run Mode + zh: 运行模式 + zh-hant: 運行模式 + ja: 実行モード + ko: 실행 모드 + ru: Режим работы + ms: Mod jalanan + pt-br: Modo de execução + required: true + type: select + values: + - label: standard + value: standard + - label: simple + value: simple + - default: admin@sub2api.local + edit: true + envKey: ADMIN_EMAIL + labelEn: Admin Email + labelZh: 管理员邮箱 + label: + en: Admin Email + zh: 管理员邮箱 + zh-hant: 管理員郵箱 + ja: 管理者メール + ko: 관리자 이메일 + ru: Email администратора + ms: E-mel pentadbir + pt-br: E-mail do administrador + required: true + type: text + - default: "" + edit: true + envKey: ADMIN_PASSWORD + labelEn: Admin Password + labelZh: 管理员密码 + label: + en: Admin Password + zh: 管理员密码 + zh-hant: 管理員密碼 + ja: 管理者パスワード + ko: 관리자 비밀번호 + ru: Пароль администратора + ms: Kata laluan pentadbir + pt-br: Senha do administrador + random: true + required: false + rule: paramComplexity + type: password + - default: "" + edit: true + envKey: JWT_SECRET + labelEn: JWT Secret + labelZh: JWT 密钥 + label: + en: JWT Secret + zh: JWT 密钥 + zh-hant: JWT 金鑰 + ja: JWT シークレット + ko: JWT 시크릿 + ru: JWT-секрет + ms: Rahsia JWT + pt-br: Segredo JWT + random: true + required: true + type: password + - default: "" + edit: true + envKey: TOTP_ENCRYPTION_KEY + labelEn: TOTP Encryption Key + labelZh: TOTP 加密密钥 + label: + en: TOTP Encryption Key + zh: TOTP 加密密钥 + zh-hant: TOTP 加密金鑰 + ja: TOTP 暗号化キー + ko: TOTP 암호화 키 + ru: Ключ шифрования TOTP + ms: Kunci penyulitan TOTP + pt-br: Chave de criptografia TOTP + random: true + required: true + type: password + - default: "" + edit: true + envKey: UPDATE_PROXY_URL + labelEn: Update Proxy URL + labelZh: 更新代理地址 + label: + en: Update Proxy URL + zh: 更新代理地址 + zh-hant: 更新代理地址 + ja: 更新プロキシ URL + ko: 업데이트 프록시 URL + ru: URL прокси для обновлений + ms: URL proksi kemas kini + pt-br: URL do proxy de atualização + required: false + rule: paramExtUrl + type: text + - default: ./data + edit: true + envKey: APP_DATA_DIR_1 + labelEn: App Data Directory + labelZh: 应用数据目录 + label: + en: App Data Directory + zh: 应用数据目录 + zh-hant: 應用資料目錄 + ja: アプリデータディレクトリ + ko: 앱 데이터 디렉터리 + ru: Каталог данных приложения + ms: Direktori data aplikasi + pt-br: Diretório de dados da aplicação + required: true + type: text diff --git a/apps/sub2api/latest/data/.gitkeep b/apps/sub2api/latest/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/sub2api/latest/docker-compose.yml b/apps/sub2api/latest/docker-compose.yml new file mode 100644 index 000000000..98df5405d --- /dev/null +++ b/apps/sub2api/latest/docker-compose.yml @@ -0,0 +1,46 @@ +services: + sub2api: + image: "weishaw/sub2api:latest" + container_name: ${CONTAINER_NAME} + restart: always + networks: + - 1panel-network + environment: + - AUTO_SETUP=true + - SERVER_HOST=0.0.0.0 + - SERVER_PORT=8080 + - SERVER_MODE=${SERVER_MODE} + - RUN_MODE=${RUN_MODE} + - DATABASE_HOST=${PANEL_DB_HOST} + - DATABASE_PORT=${PANEL_DB_PORT} + - DATABASE_USER=${PANEL_DB_USER} + - DATABASE_PASSWORD=${PANEL_DB_USER_PASSWORD} + - DATABASE_DBNAME=${PANEL_DB_NAME} + - DATABASE_SSLMODE=disable + - REDIS_HOST=${REDIS_HOST} + - REDIS_PORT=${REDIS_PORT} + - REDIS_PASSWORD=${PANEL_REDIS_ROOT_PASSWORD} + - REDIS_DB=${REDIS_DB} + - ADMIN_EMAIL=${ADMIN_EMAIL} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-} + - JWT_SECRET=${JWT_SECRET} + - JWT_EXPIRE_HOUR=24 + - TOTP_ENCRYPTION_KEY=${TOTP_ENCRYPTION_KEY} + - TZ=${TZ} + - UPDATE_PROXY_URL=${UPDATE_PROXY_URL:-} + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + volumes: + - "${APP_DATA_DIR_1}:/app/data" + labels: + createdBy: "Apps" + healthcheck: + test: ["CMD", "wget", "-q", "-T", "5", "-O", "/dev/null", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + +networks: + 1panel-network: + external: true diff --git a/apps/sub2api/latest/scripts/init.sh b/apps/sub2api/latest/scripts/init.sh new file mode 100755 index 000000000..51bfe1764 --- /dev/null +++ b/apps/sub2api/latest/scripts/init.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail + +# 当前适配使用 1Panel 数据库 / Redis 依赖注入。 +# 这里只初始化应用自身的数据目录。 +mkdir -p "${APP_DATA_DIR_1:-./data}" diff --git a/apps/sub2api/latest/scripts/uninstall.sh b/apps/sub2api/latest/scripts/uninstall.sh new file mode 100755 index 000000000..20a0bf525 --- /dev/null +++ b/apps/sub2api/latest/scripts/uninstall.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail + +# 当前适配只卸载 Sub2API 容器本身。 +# 数据库与 Redis 为 1Panel 依赖应用,不在此处删除。 +docker-compose down --volumes diff --git a/apps/sub2api/latest/scripts/upgrade.sh b/apps/sub2api/latest/scripts/upgrade.sh new file mode 100755 index 000000000..622a93268 --- /dev/null +++ b/apps/sub2api/latest/scripts/upgrade.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail + +# 升级时确保应用自身的数据目录存在。 +# 数据库与 Redis 数据由 1Panel 依赖应用管理。 +mkdir -p "${APP_DATA_DIR_1:-./data}" diff --git a/apps/sub2api/logo.png b/apps/sub2api/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7a5481d9ac0b5497e34bba088936c05d084f315e GIT binary patch literal 6603 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 JPDHLkV1h+O;WGdL literal 0 HcmV?d00001