mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-13 07:26:45 +00:00
feat(system): implement one-shot upgrade mode and enhance upgrade handling
This commit is contained in:
@@ -950,6 +950,30 @@ def restart_system(_: User = Depends(get_current_active_superuser)):
|
|||||||
global_vars.stop_system()
|
global_vars.stop_system()
|
||||||
# 执行重启
|
# 执行重启
|
||||||
ret, msg = SystemHelper.restart()
|
ret, msg = SystemHelper.restart()
|
||||||
|
if not ret:
|
||||||
|
global_vars.resume_system()
|
||||||
|
return schemas.Response(success=ret, message=msg)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upgrade", summary="升级并重启系统", response_model=schemas.Response)
|
||||||
|
def upgrade_system(
|
||||||
|
mode: Annotated[str | None, Body()] = None,
|
||||||
|
_: User = Depends(get_current_active_superuser),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
触发系统升级并重启(仅管理员)
|
||||||
|
|
||||||
|
- 当前已开启自动升级时:直接重启,由启动流程完成升级。
|
||||||
|
- 当前未开启自动升级时:写入一次性升级标记,本次重启后仅执行一次升级。
|
||||||
|
"""
|
||||||
|
if not SystemHelper.can_restart():
|
||||||
|
return schemas.Response(success=False, message="当前运行环境不支持升级操作!")
|
||||||
|
|
||||||
|
# 标识停止事件
|
||||||
|
global_vars.stop_system()
|
||||||
|
ret, msg = SystemHelper.upgrade(mode=mode or "release")
|
||||||
|
if not ret:
|
||||||
|
global_vars.resume_system()
|
||||||
return schemas.Response(success=ret, message=msg)
|
return schemas.Response(success=ret, message=msg)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import click
|
|||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from app.core.config import Settings, settings
|
from app.core.config import Settings, settings
|
||||||
|
from app.helper.system import SystemHelper
|
||||||
from version import APP_VERSION
|
from version import APP_VERSION
|
||||||
|
|
||||||
BACKEND_RUNTIME_FILE = settings.TEMP_PATH / "moviepilot.runtime.json"
|
BACKEND_RUNTIME_FILE = settings.TEMP_PATH / "moviepilot.runtime.json"
|
||||||
@@ -272,7 +273,10 @@ def _git_current_branch() -> Optional[str]:
|
|||||||
|
|
||||||
|
|
||||||
def _auto_update_mode() -> str:
|
def _auto_update_mode() -> str:
|
||||||
return str(getattr(settings, "MOVIEPILOT_AUTO_UPDATE", "") or "").strip().lower()
|
one_shot_mode = SystemHelper.consume_one_shot_update_mode()
|
||||||
|
if one_shot_mode:
|
||||||
|
return one_shot_mode
|
||||||
|
return SystemHelper.get_auto_update_mode()
|
||||||
|
|
||||||
|
|
||||||
def _resolve_auto_update_targets(mode: str) -> tuple[Optional[str], Optional[str]]:
|
def _resolve_auto_update_targets(mode: str) -> tuple[Optional[str], Optional[str]]:
|
||||||
|
|||||||
@@ -1066,6 +1066,12 @@ class GlobalVar(object):
|
|||||||
"""
|
"""
|
||||||
self.STOP_EVENT.set()
|
self.STOP_EVENT.set()
|
||||||
|
|
||||||
|
def resume_system(self):
|
||||||
|
"""
|
||||||
|
恢复系统运行标记。
|
||||||
|
"""
|
||||||
|
self.STOP_EVENT.clear()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_system_stopped(self):
|
def is_system_stopped(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class SystemHelper(ConfigReloadMixin):
|
|||||||
"""
|
"""
|
||||||
系统工具类,提供系统相关的操作和判断
|
系统工具类,提供系统相关的操作和判断
|
||||||
"""
|
"""
|
||||||
|
AUTO_UPDATE_ENABLED_VALUES = {"release", "dev"}
|
||||||
CONFIG_WATCH = {
|
CONFIG_WATCH = {
|
||||||
"DEBUG",
|
"DEBUG",
|
||||||
"LOG_LEVEL",
|
"LOG_LEVEL",
|
||||||
@@ -33,6 +34,7 @@ class SystemHelper(ConfigReloadMixin):
|
|||||||
__system_flag_file = "/var/log/nginx/__moviepilot__"
|
__system_flag_file = "/var/log/nginx/__moviepilot__"
|
||||||
__local_backend_runtime_file = settings.TEMP_PATH / "moviepilot.runtime.json"
|
__local_backend_runtime_file = settings.TEMP_PATH / "moviepilot.runtime.json"
|
||||||
__local_restart_log_file = settings.LOG_PATH / "moviepilot.restart.stdout.log"
|
__local_restart_log_file = settings.LOG_PATH / "moviepilot.restart.stdout.log"
|
||||||
|
__one_shot_update_flag_file = settings.TEMP_PATH / "moviepilot.pending_update"
|
||||||
|
|
||||||
def on_config_changed(self):
|
def on_config_changed(self):
|
||||||
logger.update_loggers()
|
logger.update_loggers()
|
||||||
@@ -85,6 +87,96 @@ class SystemHelper(ConfigReloadMixin):
|
|||||||
except (psutil.Error, TypeError, ValueError):
|
except (psutil.Error, TypeError, ValueError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def normalize_auto_update_mode(mode: Optional[str]) -> str:
|
||||||
|
"""
|
||||||
|
统一自动升级模式值,兼容历史 true 表示 release。
|
||||||
|
"""
|
||||||
|
normalized = str(mode or "").strip().lower()
|
||||||
|
return "release" if normalized == "true" else normalized
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_auto_update_mode() -> str:
|
||||||
|
"""
|
||||||
|
获取当前配置中的自动升级模式。
|
||||||
|
"""
|
||||||
|
return SystemHelper.normalize_auto_update_mode(
|
||||||
|
settings.MOVIEPILOT_AUTO_UPDATE
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_auto_update_enabled(mode: Optional[str] = None) -> bool:
|
||||||
|
"""
|
||||||
|
判断给定模式或当前配置是否启用了启动时自动升级。
|
||||||
|
"""
|
||||||
|
effective_mode = (
|
||||||
|
SystemHelper.get_auto_update_mode()
|
||||||
|
if mode is None
|
||||||
|
else SystemHelper.normalize_auto_update_mode(mode)
|
||||||
|
)
|
||||||
|
return effective_mode in SystemHelper.AUTO_UPDATE_ENABLED_VALUES
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def queue_one_shot_update(mode: str = "release") -> Tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
写入一次性升级标记,供重启后的启动流程消费。
|
||||||
|
"""
|
||||||
|
effective_mode = SystemHelper.normalize_auto_update_mode(mode)
|
||||||
|
if effective_mode not in SystemHelper.AUTO_UPDATE_ENABLED_VALUES:
|
||||||
|
return False, "升级模式仅支持 release 或 dev"
|
||||||
|
|
||||||
|
try:
|
||||||
|
SystemHelper.__one_shot_update_flag_file.parent.mkdir(
|
||||||
|
parents=True, exist_ok=True
|
||||||
|
)
|
||||||
|
SystemHelper.__one_shot_update_flag_file.write_text(
|
||||||
|
effective_mode, encoding="utf-8"
|
||||||
|
)
|
||||||
|
logger.info(f"已写入一次性升级标记,模式: {effective_mode}")
|
||||||
|
return True, ""
|
||||||
|
except OSError as err:
|
||||||
|
logger.error(f"写入一次性升级标记失败: {err}")
|
||||||
|
return False, f"写入一次性升级标记失败:{err}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def consume_one_shot_update_mode() -> Optional[str]:
|
||||||
|
"""
|
||||||
|
读取并清除一次性升级标记,避免后续启动重复执行。
|
||||||
|
"""
|
||||||
|
path = SystemHelper.__one_shot_update_flag_file
|
||||||
|
if not path.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw_mode = path.read_text(encoding="utf-8")
|
||||||
|
except OSError as err:
|
||||||
|
logger.warning(f"读取一次性升级标记失败: {err}")
|
||||||
|
raw_mode = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
path.unlink(missing_ok=True)
|
||||||
|
except OSError as err:
|
||||||
|
logger.warning(f"删除一次性升级标记失败: {err}")
|
||||||
|
|
||||||
|
effective_mode = SystemHelper.normalize_auto_update_mode(raw_mode)
|
||||||
|
if effective_mode not in SystemHelper.AUTO_UPDATE_ENABLED_VALUES:
|
||||||
|
if raw_mode:
|
||||||
|
logger.warning(f"忽略无效的一次性升级模式: {raw_mode}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
logger.info(f"检测到一次性升级标记,模式: {effective_mode}")
|
||||||
|
return effective_mode
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def clear_one_shot_update_flag() -> None:
|
||||||
|
"""
|
||||||
|
删除一次性升级标记。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
SystemHelper.__one_shot_update_flag_file.unlink(missing_ok=True)
|
||||||
|
except OSError as err:
|
||||||
|
logger.warning(f"删除一次性升级标记失败: {err}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _spawn_local_restart_helper() -> None:
|
def _spawn_local_restart_helper() -> None:
|
||||||
helper_code = (
|
helper_code = (
|
||||||
@@ -178,6 +270,8 @@ class SystemHelper(ConfigReloadMixin):
|
|||||||
return False, "当前实例不是由 moviepilot CLI 启动,无法执行内建重启!"
|
return False, "当前实例不是由 moviepilot CLI 启动,无法执行内建重启!"
|
||||||
try:
|
try:
|
||||||
SystemHelper._spawn_local_restart_helper()
|
SystemHelper._spawn_local_restart_helper()
|
||||||
|
# 复用与 Docker 相同的优雅退出路径,确保当前后端进程真正结束。
|
||||||
|
os.kill(os.getpid(), signal.SIGTERM)
|
||||||
return True, ""
|
return True, ""
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"本地 CLI 重启失败: {str(err)}")
|
logger.error(f"本地 CLI 重启失败: {str(err)}")
|
||||||
@@ -204,6 +298,34 @@ class SystemHelper(ConfigReloadMixin):
|
|||||||
logger.warning("降级为Docker API重启...")
|
logger.warning("降级为Docker API重启...")
|
||||||
return SystemHelper._docker_api_restart()
|
return SystemHelper._docker_api_restart()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def upgrade(mode: str = "release") -> Tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
触发升级并重启。
|
||||||
|
|
||||||
|
- 已开启自动升级时,直接重启,沿用当前配置。
|
||||||
|
- 未开启自动升级时,写入一次性升级标记,供下次启动时执行升级。
|
||||||
|
"""
|
||||||
|
current_mode = SystemHelper.get_auto_update_mode()
|
||||||
|
if SystemHelper.is_auto_update_enabled(current_mode):
|
||||||
|
ret, msg = SystemHelper.restart()
|
||||||
|
if not ret:
|
||||||
|
return ret, msg
|
||||||
|
if current_mode == "dev":
|
||||||
|
return True, "已检测到自动升级模式 dev,正在重启并执行升级"
|
||||||
|
return True, "已检测到自动升级已开启,正在重启并执行升级"
|
||||||
|
|
||||||
|
queued, message = SystemHelper.queue_one_shot_update(mode)
|
||||||
|
if not queued:
|
||||||
|
return False, message
|
||||||
|
|
||||||
|
ret, msg = SystemHelper.restart()
|
||||||
|
if not ret:
|
||||||
|
SystemHelper.clear_one_shot_update_flag()
|
||||||
|
return ret, msg
|
||||||
|
effective_mode = SystemHelper.normalize_auto_update_mode(mode)
|
||||||
|
return True, f"已安排一次性 {effective_mode} 升级并重启"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _start_graceful_shutdown_monitor():
|
def _start_graceful_shutdown_monitor():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -219,6 +219,25 @@ function graceful_exit() {
|
|||||||
# 使用env配置
|
# 使用env配置
|
||||||
load_config_from_app_env
|
load_config_from_app_env
|
||||||
|
|
||||||
|
# 一次性升级标记仅影响本次启动,避免把临时升级模式带入运行中的 Python 进程
|
||||||
|
ONE_SHOT_UPDATE_FLAG="${CONFIG_DIR}/temp/moviepilot.pending_update"
|
||||||
|
ONE_SHOT_UPDATE_APPLIED="false"
|
||||||
|
MOVIEPILOT_AUTO_UPDATE_ORIGINAL="${MOVIEPILOT_AUTO_UPDATE}"
|
||||||
|
if [ -f "${ONE_SHOT_UPDATE_FLAG}" ]; then
|
||||||
|
ONE_SHOT_UPDATE_MODE="$(tr -d '\r\n' < "${ONE_SHOT_UPDATE_FLAG}" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
rm -f "${ONE_SHOT_UPDATE_FLAG}"
|
||||||
|
if [ "${ONE_SHOT_UPDATE_MODE}" = "true" ]; then
|
||||||
|
ONE_SHOT_UPDATE_MODE="release"
|
||||||
|
fi
|
||||||
|
if [ "${ONE_SHOT_UPDATE_MODE}" = "release" ] || [ "${ONE_SHOT_UPDATE_MODE}" = "dev" ]; then
|
||||||
|
INFO "检测到一次性升级标记,本次启动将执行 ${ONE_SHOT_UPDATE_MODE} 升级..."
|
||||||
|
export MOVIEPILOT_AUTO_UPDATE="${ONE_SHOT_UPDATE_MODE}"
|
||||||
|
ONE_SHOT_UPDATE_APPLIED="true"
|
||||||
|
elif [ -n "${ONE_SHOT_UPDATE_MODE}" ]; then
|
||||||
|
WARN "检测到无效的一次性升级模式:${ONE_SHOT_UPDATE_MODE},已忽略"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# 生成HTTPS配置块
|
# 生成HTTPS配置块
|
||||||
if [ "${ENABLE_SSL}" = "true" ]; then
|
if [ "${ENABLE_SSL}" = "true" ]; then
|
||||||
export HTTPS_SERVER_CONF=$(cat <<EOF
|
export HTTPS_SERVER_CONF=$(cat <<EOF
|
||||||
@@ -256,6 +275,9 @@ envsubst '${NGINX_PORT}${PORT}${NGINX_CLIENT_MAX_BODY_SIZE}${ENABLE_SSL}${HTTPS_
|
|||||||
# 自动更新
|
# 自动更新
|
||||||
cd /
|
cd /
|
||||||
source /usr/local/bin/mp_update.sh
|
source /usr/local/bin/mp_update.sh
|
||||||
|
if [ "${ONE_SHOT_UPDATE_APPLIED}" = "true" ]; then
|
||||||
|
export MOVIEPILOT_AUTO_UPDATE="${MOVIEPILOT_AUTO_UPDATE_ORIGINAL}"
|
||||||
|
fi
|
||||||
cd /app || exit
|
cd /app || exit
|
||||||
|
|
||||||
# 更改 moviepilot userid 和 groupid
|
# 更改 moviepilot userid 和 groupid
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ All endpoints are under the base URL `{MP_HOST}`. Path parameters are shown as `
|
|||||||
| POST | `/api/v1/workflow/fork` | Fork shared workflow. Body: WorkflowShare JSON |
|
| POST | `/api/v1/workflow/fork` | Fork shared workflow. Body: WorkflowShare JSON |
|
||||||
| GET | `/api/v1/workflow/shares` | List shared workflows. Params: `name`, `page`, `count` |
|
| GET | `/api/v1/workflow/shares` | List shared workflows. Params: `name`, `page`, `count` |
|
||||||
|
|
||||||
### System (20 endpoints)
|
### System (21 endpoints)
|
||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|--------|------|-------------|
|
|--------|------|-------------|
|
||||||
@@ -321,6 +321,7 @@ All endpoints are under the base URL `{MP_HOST}`. Path parameters are shown as `
|
|||||||
| GET | `/api/v1/system/global` | Non-sensitive settings. Params: `token` (required) |
|
| GET | `/api/v1/system/global` | Non-sensitive settings. Params: `token` (required) |
|
||||||
| GET | `/api/v1/system/global/user` | User-related settings |
|
| GET | `/api/v1/system/global/user` | User-related settings |
|
||||||
| GET | `/api/v1/system/restart` | Restart system |
|
| GET | `/api/v1/system/restart` | Restart system |
|
||||||
|
| POST | `/api/v1/system/upgrade` | Upgrade and restart system. Body: `"release"` or `"dev"` |
|
||||||
| GET | `/api/v1/system/runscheduler` | Run scheduled service. Params: `jobid` (required) |
|
| GET | `/api/v1/system/runscheduler` | Run scheduled service. Params: `jobid` (required) |
|
||||||
| GET | `/api/v1/system/runscheduler2` | Run scheduler (API_TOKEN, use `--token-param`). Params: `jobid` |
|
| GET | `/api/v1/system/runscheduler2` | Run scheduler (API_TOKEN, use `--token-param`). Params: `jobid` |
|
||||||
| GET | `/api/v1/system/modulelist` | List loaded modules |
|
| GET | `/api/v1/system/modulelist` | List loaded modules |
|
||||||
|
|||||||
@@ -1,144 +1,74 @@
|
|||||||
---
|
---
|
||||||
name: moviepilot-update
|
name: moviepilot-update
|
||||||
version: 1
|
version: 2
|
||||||
description: Use this skill when you need to restart or upgrade MoviePilot. This skill covers system restart, version check, and manual upgrade procedures.
|
description: Use this skill when you need to check MoviePilot versions, restart MoviePilot, or trigger a MoviePilot upgrade. Prefer the built-in system APIs instead of docker commands or manual file replacement. If auto-update on restart is already enabled, just restart. If it is disabled, call the upgrade API so MoviePilot performs a one-shot upgrade and restart.
|
||||||
---
|
---
|
||||||
|
|
||||||
# MoviePilot System Update & Restart
|
# MoviePilot Update
|
||||||
|
|
||||||
> All script paths are relative to this skill file.
|
> All script paths are relative to this skill file.
|
||||||
|
|
||||||
This skill provides capabilities to restart MoviePilot service, check for updates, and perform manual upgrades.
|
Use this skill for MoviePilot restart and upgrade operations.
|
||||||
|
|
||||||
## Restart MoviePilot
|
## Setup
|
||||||
|
|
||||||
### Method 1: Using REST API (Recommended)
|
This skill reuses the `moviepilot-api` client configuration.
|
||||||
|
|
||||||
Call the restart endpoint with admin authentication:
|
Configure host and API key once:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Using moviepilot-api skill
|
python ../moviepilot-api/scripts/mp-api.py configure --host http://localhost:3000 --apikey <API_TOKEN>
|
||||||
python scripts/mp-api.py GET /api/v1/system/restart
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Or with curl:
|
## Preferred Commands
|
||||||
```bash
|
|
||||||
curl -X GET "http://localhost:3000/api/v1/system/restart" \
|
|
||||||
-H "X-API-KEY: <YOUR_API_TOKEN>"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** This API will restart the Docker container internally. The service will be briefly unavailable during restart.
|
### Check versions
|
||||||
|
|
||||||
### Method 2: Using execute_command tool
|
|
||||||
|
|
||||||
If you have admin privileges, you can execute the restart command directly:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker restart moviepilot
|
python scripts/mp-update.py versions
|
||||||
```
|
```
|
||||||
|
|
||||||
## Check for Updates
|
This calls `GET /api/v1/system/versions`.
|
||||||
|
|
||||||
### Method 1: Using REST API
|
### Restart MoviePilot
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python scripts/mp-api.py GET /api/v1/system/versions
|
python scripts/mp-update.py restart
|
||||||
```
|
```
|
||||||
|
|
||||||
This returns all available GitHub releases.
|
This calls `GET /api/v1/system/restart`.
|
||||||
|
|
||||||
### Method 2: Check current version
|
### Upgrade and restart MoviePilot
|
||||||
|
|
||||||
|
Release mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check current version
|
python scripts/mp-update.py upgrade
|
||||||
cat /app/version.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Upgrade MoviePilot
|
Dev mode:
|
||||||
|
|
||||||
### Option 1: Automatic Update (Recommended)
|
```bash
|
||||||
|
python scripts/mp-update.py upgrade dev
|
||||||
|
```
|
||||||
|
|
||||||
Set the environment variable `MOVIEPILOT_AUTO_UPDATE` and restart:
|
This calls `POST /api/v1/system/upgrade`.
|
||||||
|
|
||||||
1. **For Docker Compose users:**
|
Behavior:
|
||||||
```bash
|
|
||||||
# Edit docker-compose.yml, add environment variable:
|
|
||||||
environment:
|
|
||||||
- MOVIEPILOT_AUTO_UPDATE=release # or "dev" for dev版本
|
|
||||||
|
|
||||||
# Then restart
|
|
||||||
docker-compose down && docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **For Docker run users:**
|
- If `MOVIEPILOT_AUTO_UPDATE` is already enabled (`release` or `dev`), MoviePilot only triggers a restart and lets the normal startup flow perform the upgrade.
|
||||||
```bash
|
- If `MOVIEPILOT_AUTO_UPDATE` is disabled, MoviePilot writes a one-shot upgrade flag, restarts itself, performs that single upgrade during startup, and then continues running without changing the persisted auto-update setting.
|
||||||
docker stop moviepilot
|
|
||||||
docker rm moviepilot
|
|
||||||
docker run -d ... -e MOVIEPILOT_AUTO_UPDATE=release jxxghp/moviepilot
|
|
||||||
```
|
|
||||||
|
|
||||||
The update script (`/usr/local/bin/mp_update.sh` or `/app/docker/update.sh`) will automatically:
|
## Direct API Examples
|
||||||
- Check GitHub for latest release
|
|
||||||
- Download new backend code
|
|
||||||
- Update dependencies if changed
|
|
||||||
- Download new frontend
|
|
||||||
- Update site resources
|
|
||||||
- Restart the service
|
|
||||||
|
|
||||||
### Option 2: Manual Upgrade
|
```bash
|
||||||
|
python ../moviepilot-api/scripts/mp-api.py GET /api/v1/system/restart
|
||||||
|
python ../moviepilot-api/scripts/mp-api.py POST /api/v1/system/upgrade --json '"release"'
|
||||||
|
python ../moviepilot-api/scripts/mp-api.py POST /api/v1/system/upgrade --json '"dev"'
|
||||||
|
```
|
||||||
|
|
||||||
If you need to manually download and apply updates:
|
## Notes
|
||||||
|
|
||||||
1. **Get latest release version:**
|
- These operations require administrator authentication.
|
||||||
```bash
|
- Restart or upgrade will interrupt the current agent session. Do not rely on post-restart follow-up steps in the same run.
|
||||||
curl -s https://api.github.com/repos/jxxghp/MoviePilot/releases | grep '"tag_name"' | grep "v2" | head -1
|
- Prefer the API flow above. Only fall back to manual container commands when the API is unavailable.
|
||||||
```
|
|
||||||
|
|
||||||
2. **Download and extract backend:**
|
|
||||||
```bash
|
|
||||||
# Replace v2.x.x with actual version
|
|
||||||
curl -L -o /tmp/backend.zip https://github.com/jxxghp/MoviePilot/archive/refs/tags/v2.x.x.zip
|
|
||||||
unzip -d /tmp/backend /tmp/backend.zip
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Backup and replace:**
|
|
||||||
```bash
|
|
||||||
# Backup current installation
|
|
||||||
cp -r /app /app_backup
|
|
||||||
|
|
||||||
# Replace files (exclude config and plugins)
|
|
||||||
cp -r /tmp/backend/MoviePilot-*/* /app/
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Restart MoviePilot:**
|
|
||||||
```bash
|
|
||||||
# Use API or docker restart
|
|
||||||
python scripts/mp-api.py GET /api/v1/system/restart
|
|
||||||
```
|
|
||||||
|
|
||||||
### Important Notes
|
|
||||||
|
|
||||||
- **Backup first:** Before upgrading, backup your configuration and database
|
|
||||||
- **Dependencies:** Check if requirements.in has changes; if so, update virtual environment
|
|
||||||
- **Plugins:** The update script automatically backs up and restores plugins
|
|
||||||
- **Non-Docker:** For non-Docker installations, use `git pull` or `pip install -U moviepilot`
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
| Issue | Solution |
|
|
||||||
|-------|----------|
|
|
||||||
| Restart fails | Check if Docker daemon is running; verify container has restart policy |
|
|
||||||
| Update fails | Check network connectivity to GitHub; ensure sufficient disk space |
|
|
||||||
| Version unchanged | Verify `MOVIEPILOT_AUTO_UPDATE` environment variable is set correctly |
|
|
||||||
| Dependency errors | May need to rebuild virtual environment: `pip-compile requirements.in && pip install -r requirements.txt` |
|
|
||||||
|
|
||||||
## Environment Variables for Auto-Update
|
|
||||||
|
|
||||||
| Variable | Value | Description |
|
|
||||||
|----------|-------|-------------|
|
|
||||||
| `MOVIEPILOT_AUTO_UPDATE` | `release` | Auto-update to latest stable release |
|
|
||||||
| `MOVIEPILOT_AUTO_UPDATE` | `dev` | Auto-update to latest dev version |
|
|
||||||
| `MOVIEPILOT_AUTO_UPDATE` | `false` | Disable auto-update (default) |
|
|
||||||
| `GITHUB_TOKEN` | (token) | GitHub token for higher rate limits |
|
|
||||||
| `GITHUB_PROXY` | (url) | GitHub proxy URL for China users |
|
|
||||||
| `PROXY_HOST` | (url) | Global proxy host |
|
|
||||||
|
|||||||
62
skills/moviepilot-update/scripts/mp-update.py
Normal file
62
skills/moviepilot-update/scripts/mp-update.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
API_SCRIPT = SCRIPT_DIR.parents[1] / "moviepilot-api" / "scripts" / "mp-api.py"
|
||||||
|
|
||||||
|
|
||||||
|
def run_api_call(args: list[str]) -> int:
|
||||||
|
command = [sys.executable, str(API_SCRIPT), *args]
|
||||||
|
return_code = __import__("subprocess").run(command, check=False).returncode
|
||||||
|
return return_code
|
||||||
|
|
||||||
|
|
||||||
|
def print_usage() -> None:
|
||||||
|
print(
|
||||||
|
"Usage:\n"
|
||||||
|
f" python {Path(sys.argv[0]).name} versions\n"
|
||||||
|
f" python {Path(sys.argv[0]).name} restart\n"
|
||||||
|
f" python {Path(sys.argv[0]).name} upgrade [release|dev]"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
if not argv or argv[0] in {"-h", "--help", "help"}:
|
||||||
|
print_usage()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
command = argv[0].lower()
|
||||||
|
if command == "versions":
|
||||||
|
return run_api_call(["GET", "/api/v1/system/versions"])
|
||||||
|
|
||||||
|
if command == "restart":
|
||||||
|
return run_api_call(["GET", "/api/v1/system/restart"])
|
||||||
|
|
||||||
|
if command == "upgrade":
|
||||||
|
mode = (argv[1] if len(argv) > 1 else "release").strip().lower()
|
||||||
|
if mode == "true":
|
||||||
|
mode = "release"
|
||||||
|
if mode not in {"release", "dev"}:
|
||||||
|
print("Error: mode must be release or dev", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
return run_api_call([
|
||||||
|
"POST",
|
||||||
|
"/api/v1/system/upgrade",
|
||||||
|
"--json",
|
||||||
|
json.dumps(mode, ensure_ascii=False),
|
||||||
|
])
|
||||||
|
|
||||||
|
print(f"Error: unknown command: {command}", file=sys.stderr)
|
||||||
|
print_usage()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user