Files
MoonTVPlus/.github/workflows/edgeone-deploy.yml
T
mtvpls-turso 8e464c463b feat: add Turso (libSQL) storage support for EdgeOne deployment
- Add TursoAdapter implementing DatabaseAdapter interface using @libsql/client
- Add 'turso' storage type to db.ts storage selector (reuses D1Storage)
- Add init-turso.js script for database migration and admin initialization
- Add @libsql/client dependency to package.json
- Update next.config.js to exclude @libsql/client from client-side bundle
- Update edgeone-deploy.yml workflow with TURSO_URL and TURSO_TOKEN secrets
- Update edgeone-build.mjs runtime env keys to include Turso variables
- Update .env.edgeone.example with Turso deployment instructions
- Update README.md with Turso as recommended storage for EdgeOne

Turso provides free SQLite-compatible cloud database (libSQL) with:
- 500 databases, 9GB storage, 1B row reads/month on free tier
- HTTP API compatible with edge/serverless environments
- Full SQLite syntax compatibility, reuses existing migrations
2026-07-11 17:04:27 +00:00

248 lines
12 KiB
YAML

name: Deploy to EdgeOne
on:
workflow_dispatch:
inputs:
project_name:
description: 'EdgeOne Pages 项目名'
required: false
default: 'moontvplus'
type: string
area:
description: '部署区域:overseas = 全球可用区(不含中国大陆),global = 全球可用区'
required: false
default: 'overseas'
type: choice
options:
- overseas
- global
sync_environment:
description: '部署后同步 GitHub Secrets 到 EdgeOne 环境变量,并再次部署使其生效'
required: false
default: true
type: boolean
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
deployments: write
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy to EdgeOne Pages
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Export build environment variables
env:
NEXT_PUBLIC_STORAGE_TYPE: ${{ secrets.NEXT_PUBLIC_STORAGE_TYPE }}
NEXT_PUBLIC_SITE_NAME: ${{ secrets.NEXT_PUBLIC_SITE_NAME }}
NEXT_PUBLIC_SEARCH_MAX_PAGE: ${{ secrets.NEXT_PUBLIC_SEARCH_MAX_PAGE }}
NEXT_PUBLIC_DOUBAN_PROXY_TYPE: ${{ secrets.NEXT_PUBLIC_DOUBAN_PROXY_TYPE }}
NEXT_PUBLIC_DOUBAN_PROXY: ${{ secrets.NEXT_PUBLIC_DOUBAN_PROXY }}
NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE: ${{ secrets.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE }}
NEXT_PUBLIC_DOUBAN_IMAGE_PROXY: ${{ secrets.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY }}
NEXT_PUBLIC_DISABLE_YELLOW_FILTER: ${{ secrets.NEXT_PUBLIC_DISABLE_YELLOW_FILTER }}
NEXT_PUBLIC_FLUID_SEARCH: ${{ secrets.NEXT_PUBLIC_FLUID_SEARCH }}
NEXT_PUBLIC_PROXY_M3U8_TOKEN: ${{ secrets.NEXT_PUBLIC_PROXY_M3U8_TOKEN }}
NEXT_PUBLIC_DANMAKU_CACHE_EXPIRE_MINUTES: ${{ secrets.NEXT_PUBLIC_DANMAKU_CACHE_EXPIRE_MINUTES }}
NEXT_PUBLIC_VOICE_CHAT_STRATEGY: ${{ secrets.NEXT_PUBLIC_VOICE_CHAT_STRATEGY }}
NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD: ${{ secrets.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD }}
NEXT_PUBLIC_ENABLE_SOURCE_SEARCH: ${{ secrets.NEXT_PUBLIC_ENABLE_SOURCE_SEARCH }}
NEXT_PUBLIC_IMAGE_PROXY: ${{ secrets.NEXT_PUBLIC_IMAGE_PROXY }}
NEXT_PUBLIC_IMAGE_PROXY_PREFIX: ${{ secrets.NEXT_PUBLIC_IMAGE_PROXY_PREFIX }}
NEXT_PUBLIC_STORAGE_PREFIX: ${{ secrets.NEXT_PUBLIC_STORAGE_PREFIX }}
NEXT_PUBLIC_ENABLE_STREAM_PROXY: ${{ secrets.NEXT_PUBLIC_ENABLE_STREAM_PROXY }}
NEXT_PUBLIC_STREAM_PROXY_URL: ${{ secrets.NEXT_PUBLIC_STREAM_PROXY_URL }}
NEXT_PUBLIC_ENABLE_TURNSTILE: ${{ secrets.NEXT_PUBLIC_ENABLE_TURNSTILE }}
NEXT_PUBLIC_TURNSTILE_SITE_KEY: ${{ secrets.NEXT_PUBLIC_TURNSTILE_SITE_KEY }}
NEXT_PUBLIC_THEME: ${{ secrets.NEXT_PUBLIC_THEME }}
NEXT_PUBLIC_THEME_COLOR: ${{ secrets.NEXT_PUBLIC_THEME_COLOR }}
NEXT_PUBLIC_APP_URL: ${{ secrets.NEXT_PUBLIC_APP_URL }}
NEXT_PUBLIC_BASE_PATH: ${{ secrets.NEXT_PUBLIC_BASE_PATH }}
USERNAME: ${{ secrets.USERNAME }}
PASSWORD: ${{ secrets.PASSWORD }}
UPSTASH_URL: ${{ secrets.UPSTASH_URL }}
UPSTASH_TOKEN: ${{ secrets.UPSTASH_TOKEN }}
TURSO_URL: ${{ secrets.TURSO_URL }}
TURSO_TOKEN: ${{ secrets.TURSO_TOKEN }}
TMDB_API_KEY: ${{ secrets.TMDB_API_KEY }}
TMDB_IMAGE_DOMAIN: ${{ secrets.TMDB_IMAGE_DOMAIN }}
TMDB_PROXY: ${{ secrets.TMDB_PROXY }}
TMDB_REVERSE_PROXY: ${{ secrets.TMDB_REVERSE_PROXY }}
DANMAKU_API_BASE: ${{ secrets.DANMAKU_API_BASE }}
DANMAKU_API_TOKEN: ${{ secrets.DANMAKU_API_TOKEN }}
ANNOUNCEMENT: ${{ secrets.ANNOUNCEMENT }}
CRON_PASSWORD: ${{ secrets.CRON_PASSWORD }}
SITE_BASE: ${{ secrets.SITE_BASE }}
REDIS_URL: ${{ secrets.REDIS_URL }}
KVROCKS_URL: ${{ secrets.KVROCKS_URL }}
INIT_CONFIG: ${{ secrets.INIT_CONFIG }}
CONFIG_SUBSCRIPTION_URL: ${{ secrets.CONFIG_SUBSCRIPTION_URL }}
ENABLE_REGISTER: ${{ secrets.ENABLE_REGISTER }}
ENABLE_TVBOX_SUBSCRIBE: ${{ secrets.ENABLE_TVBOX_SUBSCRIBE }}
TVBOX_SUBSCRIBE_PATH: ${{ secrets.TVBOX_SUBSCRIBE_PATH }}
TVBOX_SUBSCRIBE_TOKEN: ${{ secrets.TVBOX_SUBSCRIBE_TOKEN }}
WATCH_ROOM_ENABLED: ${{ secrets.WATCH_ROOM_ENABLED }}
WATCH_ROOM_WS_URL: ${{ secrets.WATCH_ROOM_WS_URL }}
WATCH_ROOM_SERVER_URL: ${{ secrets.WATCH_ROOM_SERVER_URL }}
WATCH_ROOM_EXTERNAL_SERVER_AUTH: ${{ secrets.WATCH_ROOM_EXTERNAL_SERVER_AUTH }}
WATCH_ROOM_EXTERNAL_SERVER_URL: ${{ secrets.WATCH_ROOM_EXTERNAL_SERVER_URL }}
WATCH_ROOM_SERVER_TYPE: ${{ secrets.WATCH_ROOM_SERVER_TYPE }}
WEBSOCKET_SECRET: ${{ secrets.WEBSOCKET_SECRET }}
WEB_PUSH_EMAIL: ${{ secrets.WEB_PUSH_EMAIL }}
WEB_PUSH_PRIVATE_KEY: ${{ secrets.WEB_PUSH_PRIVATE_KEY }}
WEB_PUSH_PUBLIC_KEY: ${{ secrets.WEB_PUSH_PUBLIC_KEY }}
WEB_PUSH_BASEURL: ${{ secrets.WEB_PUSH_BASEURL }}
TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }}
run: |
append_env() {
local key="$1"
local value="${!key-}"
if [ -n "$value" ]; then
{
echo "$key<<__EDGEONE_ENV_$key__"
echo "$value"
echo "__EDGEONE_ENV_$key__"
} >> "$GITHUB_ENV"
fi
}
keys=(
NEXT_PUBLIC_STORAGE_TYPE NEXT_PUBLIC_SITE_NAME NEXT_PUBLIC_SEARCH_MAX_PAGE
NEXT_PUBLIC_DOUBAN_PROXY_TYPE NEXT_PUBLIC_DOUBAN_PROXY
NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE NEXT_PUBLIC_DOUBAN_IMAGE_PROXY
NEXT_PUBLIC_DISABLE_YELLOW_FILTER NEXT_PUBLIC_FLUID_SEARCH
NEXT_PUBLIC_PROXY_M3U8_TOKEN NEXT_PUBLIC_DANMAKU_CACHE_EXPIRE_MINUTES
NEXT_PUBLIC_VOICE_CHAT_STRATEGY NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD
NEXT_PUBLIC_ENABLE_SOURCE_SEARCH NEXT_PUBLIC_IMAGE_PROXY NEXT_PUBLIC_IMAGE_PROXY_PREFIX
NEXT_PUBLIC_STORAGE_PREFIX NEXT_PUBLIC_ENABLE_STREAM_PROXY NEXT_PUBLIC_STREAM_PROXY_URL
NEXT_PUBLIC_ENABLE_TURNSTILE NEXT_PUBLIC_TURNSTILE_SITE_KEY
NEXT_PUBLIC_THEME NEXT_PUBLIC_THEME_COLOR NEXT_PUBLIC_APP_URL NEXT_PUBLIC_BASE_PATH
USERNAME PASSWORD UPSTASH_URL UPSTASH_TOKEN TURSO_URL TURSO_TOKEN
TMDB_API_KEY TMDB_IMAGE_DOMAIN TMDB_PROXY TMDB_REVERSE_PROXY
DANMAKU_API_BASE DANMAKU_API_TOKEN ANNOUNCEMENT CRON_PASSWORD SITE_BASE
REDIS_URL KVROCKS_URL INIT_CONFIG CONFIG_SUBSCRIPTION_URL ENABLE_REGISTER
ENABLE_TVBOX_SUBSCRIBE TVBOX_SUBSCRIBE_PATH TVBOX_SUBSCRIBE_TOKEN
WATCH_ROOM_ENABLED WATCH_ROOM_WS_URL WATCH_ROOM_SERVER_URL
WATCH_ROOM_EXTERNAL_SERVER_AUTH WATCH_ROOM_EXTERNAL_SERVER_URL WATCH_ROOM_SERVER_TYPE
WEBSOCKET_SECRET WEB_PUSH_EMAIL WEB_PUSH_PRIVATE_KEY WEB_PUSH_PUBLIC_KEY WEB_PUSH_BASEURL
TURNSTILE_SECRET_KEY
)
for key in "${keys[@]}"; do
append_env "$key"
done
echo "BUILD_TARGET=edgeone" >> "$GITHUB_ENV"
echo "EDGEONE_PAGES=1" >> "$GITHUB_ENV"
echo "NODE_ENV=production" >> "$GITHUB_ENV"
- name: Build for EdgeOne
run: pnpm run build:edgeone
- name: Deploy to EdgeOne
id: deploy
env:
EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }}
EDGEONE_PROJECT_NAME: ${{ inputs.project_name }}
EDGEONE_AREA: ${{ inputs.area }}
run: |
if [ -z "$EDGEONE_API_TOKEN" ]; then
echo "EDGEONE_API_TOKEN secret is required" >&2
exit 1
fi
pnpm exec edgeone makers deploy .edgeone \
-n "$EDGEONE_PROJECT_NAME" \
-t "$EDGEONE_API_TOKEN" \
-a "$EDGEONE_AREA" | tee edgeone-deploy.log
deploy_url=$(grep '^EDGEONE_DEPLOY_URL=' edgeone-deploy.log | tail -n 1 | cut -d= -f2-)
project_id=$(grep '^EDGEONE_PROJECT_ID=' edgeone-deploy.log | tail -n 1 | cut -d= -f2-)
echo "deploy_url=$deploy_url" >> "$GITHUB_OUTPUT"
echo "project_id=$project_id" >> "$GITHUB_OUTPUT"
- name: Sync EdgeOne environment variables
if: ${{ inputs.sync_environment }}
env:
EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }}
run: |
keys=(
NEXT_PUBLIC_STORAGE_TYPE NEXT_PUBLIC_SITE_NAME NEXT_PUBLIC_SEARCH_MAX_PAGE
NEXT_PUBLIC_DOUBAN_PROXY_TYPE NEXT_PUBLIC_DOUBAN_PROXY
NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE NEXT_PUBLIC_DOUBAN_IMAGE_PROXY
NEXT_PUBLIC_DISABLE_YELLOW_FILTER NEXT_PUBLIC_FLUID_SEARCH
NEXT_PUBLIC_PROXY_M3U8_TOKEN NEXT_PUBLIC_DANMAKU_CACHE_EXPIRE_MINUTES
NEXT_PUBLIC_VOICE_CHAT_STRATEGY NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD
NEXT_PUBLIC_ENABLE_SOURCE_SEARCH NEXT_PUBLIC_IMAGE_PROXY NEXT_PUBLIC_IMAGE_PROXY_PREFIX
NEXT_PUBLIC_STORAGE_PREFIX NEXT_PUBLIC_ENABLE_STREAM_PROXY NEXT_PUBLIC_STREAM_PROXY_URL
NEXT_PUBLIC_ENABLE_TURNSTILE NEXT_PUBLIC_TURNSTILE_SITE_KEY
NEXT_PUBLIC_THEME NEXT_PUBLIC_THEME_COLOR NEXT_PUBLIC_APP_URL NEXT_PUBLIC_BASE_PATH
USERNAME PASSWORD UPSTASH_URL UPSTASH_TOKEN TURSO_URL TURSO_TOKEN
TMDB_API_KEY TMDB_IMAGE_DOMAIN TMDB_PROXY TMDB_REVERSE_PROXY
DANMAKU_API_BASE DANMAKU_API_TOKEN ANNOUNCEMENT CRON_PASSWORD SITE_BASE
REDIS_URL KVROCKS_URL INIT_CONFIG CONFIG_SUBSCRIPTION_URL ENABLE_REGISTER
ENABLE_TVBOX_SUBSCRIBE TVBOX_SUBSCRIBE_PATH TVBOX_SUBSCRIBE_TOKEN
WATCH_ROOM_ENABLED WATCH_ROOM_WS_URL WATCH_ROOM_SERVER_URL
WATCH_ROOM_EXTERNAL_SERVER_AUTH WATCH_ROOM_EXTERNAL_SERVER_URL WATCH_ROOM_SERVER_TYPE
WEBSOCKET_SECRET WEB_PUSH_EMAIL WEB_PUSH_PRIVATE_KEY WEB_PUSH_PUBLIC_KEY WEB_PUSH_BASEURL
TURNSTILE_SECRET_KEY
)
for key in "${keys[@]}"; do
value="${!key-}"
if [ -n "$value" ]; then
echo "Syncing $key"
pnpm exec edgeone makers env set "$key" "$value" -t "$EDGEONE_API_TOKEN"
fi
done
- name: Redeploy after environment sync
if: ${{ inputs.sync_environment }}
id: redeploy
env:
EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }}
EDGEONE_PROJECT_NAME: ${{ inputs.project_name }}
EDGEONE_AREA: ${{ inputs.area }}
run: |
pnpm exec edgeone makers deploy .edgeone \
-n "$EDGEONE_PROJECT_NAME" \
-t "$EDGEONE_API_TOKEN" \
-a "$EDGEONE_AREA" | tee edgeone-redeploy.log
deploy_url=$(grep '^EDGEONE_DEPLOY_URL=' edgeone-redeploy.log | tail -n 1 | cut -d= -f2-)
project_id=$(grep '^EDGEONE_PROJECT_ID=' edgeone-redeploy.log | tail -n 1 | cut -d= -f2-)
echo "deploy_url=$deploy_url" >> "$GITHUB_OUTPUT"
echo "project_id=$project_id" >> "$GITHUB_OUTPUT"
- name: Deployment summary
run: |
final_url="${{ steps.redeploy.outputs.deploy_url || steps.deploy.outputs.deploy_url }}"
project_id="${{ steps.redeploy.outputs.project_id || steps.deploy.outputs.project_id }}"
{
echo "## EdgeOne deployment"
echo
echo "- Project ID: $project_id"
echo "- Deploy URL: $final_url"
echo "- Area: ${{ inputs.area }}"
} >> "$GITHUB_STEP_SUMMARY"