mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-06 20:42:43 +08: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()
|
||||
# 执行重启
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import click
|
||||
import psutil
|
||||
|
||||
from app.core.config import Settings, settings
|
||||
from app.helper.system import SystemHelper
|
||||
from version import APP_VERSION
|
||||
|
||||
BACKEND_RUNTIME_FILE = settings.TEMP_PATH / "moviepilot.runtime.json"
|
||||
@@ -272,7 +273,10 @@ def _git_current_branch() -> Optional[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]]:
|
||||
|
||||
@@ -1066,6 +1066,12 @@ class GlobalVar(object):
|
||||
"""
|
||||
self.STOP_EVENT.set()
|
||||
|
||||
def resume_system(self):
|
||||
"""
|
||||
恢复系统运行标记。
|
||||
"""
|
||||
self.STOP_EVENT.clear()
|
||||
|
||||
@property
|
||||
def is_system_stopped(self):
|
||||
"""
|
||||
|
||||
@@ -21,6 +21,7 @@ class SystemHelper(ConfigReloadMixin):
|
||||
"""
|
||||
系统工具类,提供系统相关的操作和判断
|
||||
"""
|
||||
AUTO_UPDATE_ENABLED_VALUES = {"release", "dev"}
|
||||
CONFIG_WATCH = {
|
||||
"DEBUG",
|
||||
"LOG_LEVEL",
|
||||
@@ -33,6 +34,7 @@ class SystemHelper(ConfigReloadMixin):
|
||||
__system_flag_file = "/var/log/nginx/__moviepilot__"
|
||||
__local_backend_runtime_file = settings.TEMP_PATH / "moviepilot.runtime.json"
|
||||
__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):
|
||||
logger.update_loggers()
|
||||
@@ -85,6 +87,96 @@ class SystemHelper(ConfigReloadMixin):
|
||||
except (psutil.Error, TypeError, ValueError):
|
||||
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
|
||||
def _spawn_local_restart_helper() -> None:
|
||||
helper_code = (
|
||||
@@ -178,6 +270,8 @@ class SystemHelper(ConfigReloadMixin):
|
||||
return False, "当前实例不是由 moviepilot CLI 启动,无法执行内建重启!"
|
||||
try:
|
||||
SystemHelper._spawn_local_restart_helper()
|
||||
# 复用与 Docker 相同的优雅退出路径,确保当前后端进程真正结束。
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
return True, ""
|
||||
except Exception as err:
|
||||
logger.error(f"本地 CLI 重启失败: {str(err)}")
|
||||
@@ -204,6 +298,34 @@ class SystemHelper(ConfigReloadMixin):
|
||||
logger.warning("降级为Docker API重启...")
|
||||
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
|
||||
def _start_graceful_shutdown_monitor():
|
||||
"""
|
||||
|
||||
@@ -219,6 +219,25 @@ function graceful_exit() {
|
||||
# 使用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配置块
|
||||
if [ "${ENABLE_SSL}" = "true" ]; then
|
||||
export HTTPS_SERVER_CONF=$(cat <<EOF
|
||||
@@ -256,6 +275,9 @@ envsubst '${NGINX_PORT}${PORT}${NGINX_CLIENT_MAX_BODY_SIZE}${ENABLE_SSL}${HTTPS_
|
||||
# 自动更新
|
||||
cd /
|
||||
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
|
||||
|
||||
# 更改 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 |
|
||||
| GET | `/api/v1/workflow/shares` | List shared workflows. Params: `name`, `page`, `count` |
|
||||
|
||||
### System (20 endpoints)
|
||||
### System (21 endpoints)
|
||||
|
||||
| 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/user` | User-related settings |
|
||||
| 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/runscheduler2` | Run scheduler (API_TOKEN, use `--token-param`). Params: `jobid` |
|
||||
| GET | `/api/v1/system/modulelist` | List loaded modules |
|
||||
|
||||
@@ -1,144 +1,74 @@
|
||||
---
|
||||
name: moviepilot-update
|
||||
version: 1
|
||||
description: Use this skill when you need to restart or upgrade MoviePilot. This skill covers system restart, version check, and manual upgrade procedures.
|
||||
version: 2
|
||||
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.
|
||||
|
||||
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
|
||||
# Using moviepilot-api skill
|
||||
python scripts/mp-api.py GET /api/v1/system/restart
|
||||
python ../moviepilot-api/scripts/mp-api.py configure --host http://localhost:3000 --apikey <API_TOKEN>
|
||||
```
|
||||
|
||||
Or with curl:
|
||||
```bash
|
||||
curl -X GET "http://localhost:3000/api/v1/system/restart" \
|
||||
-H "X-API-KEY: <YOUR_API_TOKEN>"
|
||||
```
|
||||
## Preferred Commands
|
||||
|
||||
**Note:** This API will restart the Docker container internally. The service will be briefly unavailable during restart.
|
||||
|
||||
### Method 2: Using execute_command tool
|
||||
|
||||
If you have admin privileges, you can execute the restart command directly:
|
||||
### Check versions
|
||||
|
||||
```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
|
||||
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
|
||||
# Check current version
|
||||
cat /app/version.py
|
||||
python scripts/mp-update.py upgrade
|
||||
```
|
||||
|
||||
## 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:**
|
||||
```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
|
||||
```
|
||||
Behavior:
|
||||
|
||||
2. **For Docker run users:**
|
||||
```bash
|
||||
docker stop moviepilot
|
||||
docker rm moviepilot
|
||||
docker run -d ... -e MOVIEPILOT_AUTO_UPDATE=release jxxghp/moviepilot
|
||||
```
|
||||
- If `MOVIEPILOT_AUTO_UPDATE` is already enabled (`release` or `dev`), MoviePilot only triggers a restart and lets the normal startup flow perform the upgrade.
|
||||
- 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.
|
||||
|
||||
The update script (`/usr/local/bin/mp_update.sh` or `/app/docker/update.sh`) will automatically:
|
||||
- Check GitHub for latest release
|
||||
- Download new backend code
|
||||
- Update dependencies if changed
|
||||
- Download new frontend
|
||||
- Update site resources
|
||||
- Restart the service
|
||||
## Direct API Examples
|
||||
|
||||
### 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:**
|
||||
```bash
|
||||
curl -s https://api.github.com/repos/jxxghp/MoviePilot/releases | grep '"tag_name"' | grep "v2" | head -1
|
||||
```
|
||||
|
||||
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 |
|
||||
- These operations require administrator authentication.
|
||||
- Restart or upgrade will interrupt the current agent session. Do not rely on post-restart follow-up steps in the same run.
|
||||
- Prefer the API flow above. Only fall back to manual container commands when the API is unavailable.
|
||||
|
||||
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