-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat: add auto-update with backup, rollback, and retention #8814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Dr1985
wants to merge
3
commits into
AstrBotDevs:master
Choose a base branch
from
Dr1985:fix-for-issue-number-8686
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
astrbot/builtin_stars/builtin_commands/commands/update.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| """Update commands for AstrBot. | ||
|
|
||
| Provides /update subcommands: check, now, auto, status. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from astrbot.api.event import MessageChain | ||
| from astrbot.core.config.default import VERSION | ||
|
|
||
| if TYPE_CHECKING: | ||
| from astrbot.api.event import AstrMessageEvent | ||
| from astrbot.api.star import Context | ||
|
|
||
|
|
||
| class UpdateCommands: | ||
| """Chat commands for checking and triggering AstrBot updates.""" | ||
|
|
||
| def __init__(self, context: Context) -> None: | ||
| self.context = context | ||
| self._update_task: asyncio.Task | None = None | ||
|
|
||
| async def update_check(self, event: AstrMessageEvent) -> None: | ||
| """Check for new AstrBot versions.""" | ||
| await event.send(MessageChain().message("🔍 Checking for updates...")) | ||
|
|
||
| auto_update_mgr = self._get_auto_update_manager() | ||
| if auto_update_mgr is None: | ||
| await event.send( | ||
| MessageChain().message("⚠️ Update manager is not available.") | ||
| ) | ||
| return | ||
|
|
||
| try: | ||
| result = await auto_update_mgr.check_now() | ||
| except Exception as exc: | ||
| await event.send( | ||
| MessageChain().message(f"❌ Failed to check for updates: {exc}") | ||
| ) | ||
| return | ||
|
|
||
| if result.get("error"): | ||
| await event.send( | ||
| MessageChain().message( | ||
| f"❌ Failed to check for updates: {result['error']}" | ||
| ) | ||
| ) | ||
| return | ||
|
|
||
| if result["has_update"]: | ||
| msg = ( | ||
| f"🔔 A new version is available!\n" | ||
| f"Current: {result['current_version']}\n" | ||
| f"Latest: {result['latest_version']}\n" | ||
| f"Published: {result.get('published_at', 'N/A')}\n\n" | ||
| f"Use /update now to update." | ||
| ) | ||
| else: | ||
| msg = ( | ||
| f"✅ You are running the latest version.\n" | ||
| f"Current: {result['current_version']}" | ||
| ) | ||
|
|
||
| await event.send(MessageChain().message(msg)) | ||
|
|
||
| async def update_now( | ||
| self, event: AstrMessageEvent, version: str | None = None | ||
| ) -> None: | ||
| """Trigger an update immediately. | ||
|
|
||
| Args: | ||
| version: Specific version tag to update to, or None for latest. | ||
| """ | ||
| auto_update_mgr = self._get_auto_update_manager() | ||
| if auto_update_mgr is None: | ||
| await event.send( | ||
| MessageChain().message("⚠️ Update manager is not available.") | ||
| ) | ||
| return | ||
|
|
||
| await event.send( | ||
| MessageChain().message( | ||
| f"⏳ Starting update to {version or 'latest version'}...\n" | ||
| f"A backup will be created before the update.\n" | ||
| f"The bot will restart after the update is complete." | ||
| ) | ||
| ) | ||
|
|
||
| try: | ||
| # 在后台触发更新,让当前消息先发出去 | ||
| # 保存任务引用,防止被 Python 垃圾回收机制提前销毁 | ||
| self._update_task = asyncio.create_task( | ||
| self._trigger_update_with_delay(auto_update_mgr, version) | ||
| ) | ||
| except Exception as exc: | ||
| await event.send( | ||
| MessageChain().message(f"❌ Failed to trigger update: {exc}") | ||
| ) | ||
|
|
||
| async def update_auto(self, event: AstrMessageEvent, action: str) -> None: | ||
| """Toggle auto-update on/off. | ||
|
|
||
| Args: | ||
| action: "on" or "off". | ||
| """ | ||
| action = action.strip().lower() if action else "" | ||
| if action not in ("on", "off"): | ||
| await event.send( | ||
| MessageChain().message( | ||
| "Usage: /update auto on|off\n" | ||
| " on — Enable automatic updates\n" | ||
| " off — Disable automatic updates" | ||
| ) | ||
| ) | ||
| return | ||
|
|
||
| enable = action == "on" | ||
|
|
||
| # 更新运行时配置 | ||
| config = self.context.get_config() | ||
| if "auto_update" not in config: | ||
| config["auto_update"] = {} | ||
| config["auto_update"]["enabled"] = enable | ||
|
|
||
| await event.send( | ||
| MessageChain().message( | ||
| f"{'✅' if enable else '🛑'} Auto-update is now " | ||
| f"{'ENABLED' if enable else 'DISABLED'}.\n" | ||
| + ( | ||
| "AstrBot will automatically check for and apply updates." | ||
| if enable | ||
| else "AstrBot will NOT automatically update itself." | ||
| ) | ||
| ) | ||
| ) | ||
|
|
||
| async def update_status(self, event: AstrMessageEvent) -> None: | ||
| """Show current version and auto-update status.""" | ||
| auto_update_mgr = self._get_auto_update_manager() | ||
| config = self.context.get_config() | ||
| auto_cfg = config.get("auto_update", {}) | ||
|
|
||
| enabled = auto_cfg.get("enabled", False) | ||
| check_interval_h = (auto_cfg.get("check_interval", 86400)) / 3600 | ||
| retention_days = auto_cfg.get("backup_retention_days", 14) | ||
|
|
||
| new_version = None | ||
| if auto_update_mgr: | ||
| new_version = auto_update_mgr.get_new_version_info() | ||
|
|
||
| msg = ( | ||
| f"📋 AstrBot Update Status\n" | ||
| f"Current version: {VERSION}\n" | ||
| f"Auto-update: {'✅ Enabled' if enabled else '⛔ Disabled'}\n" | ||
| f"Check interval: {check_interval_h:.0f}h\n" | ||
| f"Backup retention: {retention_days} days\n" | ||
| ) | ||
| if new_version: | ||
| msg += f"New version available: {new_version}\n" | ||
|
|
||
| msg += "\nCommands:\n" | ||
| msg += " /update check — Check for new version\n" | ||
| msg += " /update now — Update now\n" | ||
| msg += " /update auto on|off — Toggle auto-update\n" | ||
|
|
||
| await event.send(MessageChain().message(msg)) | ||
|
|
||
| # ---- helpers ---- | ||
|
|
||
| @staticmethod | ||
| async def _trigger_update_with_delay( | ||
| auto_update_mgr, version: str | None = None | ||
| ) -> None: | ||
| """Delay then trigger update, giving the chat message time to send.""" | ||
| await asyncio.sleep(3) | ||
| await auto_update_mgr.trigger_update(version) | ||
|
|
||
| def _get_auto_update_manager(self): | ||
| """Get the AutoUpdateManager from core_lifecycle via star context.""" | ||
| core_lifecycle = getattr(self.context, "core_lifecycle", None) | ||
| if core_lifecycle is None: | ||
| return None | ||
| return getattr(core_lifecycle, "auto_update_manager", None) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在
update_now中,使用asyncio.create_task在后台异步执行更新流程。但在 Python 的asyncio中,如果不对创建的Task对象保留强引用,该任务在执行完毕前可能会被垃圾回收器(Garbage Collector)提前销毁,导致更新流程意外中断。\n\n建议将返回的Task对象保存到实例属性中(例如self._update_task),以确保其生命周期安全。