| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- """Install the bundled Hermes plugin; preserve disabled settings and keep a backup.
- Run with the Hermes virtualenv Python. This command does not restart Hermes.
- """
- import argparse
- import json
- import os
- import re
- import shutil
- import tempfile
- import uuid
- from datetime import datetime, timezone
- from pathlib import Path
- import yaml
- def install(root, profile, source):
- if not re.fullmatch(r'[a-z0-9][a-z0-9_-]{0,63}', profile):
- raise ValueError('Profile 名称无效')
- home = root if profile == 'default' else root / 'profiles' / profile
- config_path = home / 'config.yaml'
- if not config_path.is_file() or config_path.is_symlink():
- raise ValueError('Profile 配置不存在或是符号链接')
- original = config_path.read_bytes()
- config = yaml.safe_load(original) or {}
- if not isinstance(config, dict):
- raise ValueError('Profile 配置格式无效')
- plugins = config.setdefault('plugins', {})
- if not isinstance(plugins, dict):
- raise ValueError('Profile 插件配置格式无效')
- for key in ('enabled', 'disabled'):
- if key in plugins and not isinstance(plugins[key], list):
- raise ValueError('Profile 插件列表格式无效')
- name = 'yaoyao-bot-bridge'
- disabled = name in plugins.get('disabled', [])
- if not disabled and name not in plugins.setdefault('enabled', []):
- plugins['enabled'].append(name)
- directory = home / 'plugins'
- target = directory / name
- if directory.is_symlink() or target.is_symlink():
- raise ValueError('插件目录不能是符号链接')
- directory.mkdir(parents=True, exist_ok=True)
- stamp = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ') + '-' + uuid.uuid4().hex[:8]
- backup = home / 'backups' / ('yaoyao-bridge-' + stamp)
- backup.mkdir(parents=True, mode=0o700)
- (backup / 'config.yaml').write_bytes(original)
- (backup / 'config.yaml').chmod(0o600)
- staged = directory / ('.yaoyao-bridge-' + uuid.uuid4().hex)
- previous = backup / name
- moved = False
- activated = False
- try:
- shutil.copytree(source, staged, ignore=shutil.ignore_patterns('__pycache__', '*.pyc', 'tests'))
- if target.exists():
- target.rename(previous)
- moved = True
- staged.rename(target)
- activated = True
- fd, temporary = tempfile.mkstemp(prefix='.config-yaoyao-', dir=home)
- try:
- with os.fdopen(fd, 'w') as stream:
- yaml.safe_dump(config, stream, allow_unicode=True, sort_keys=False)
- os.replace(temporary, config_path)
- finally:
- Path(temporary).unlink(missing_ok=True)
- except BaseException:
- if activated:
- shutil.rmtree(target)
- if moved:
- previous.rename(target)
- config_path.write_bytes(original)
- raise
- finally:
- if staged.exists():
- shutil.rmtree(staged)
- return {'profile': profile, 'installed': str(target), 'backup': str(backup), 'disabled': disabled,
- 'message': '已更新,保留显式禁用设置' if disabled else '已更新并启用;空闲时重新加载 Hermes 后生效'}
- def main():
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--hermes-home', required=True, type=Path)
- parser.add_argument('--profile', action='append', default=[])
- args = parser.parse_args()
- source = Path(__file__).resolve().parent / 'hermes-bots-bridge'
- if not source.is_dir():
- source = Path(__file__).resolve().parents[1] / 'integrations' / 'hermes-bots-bridge'
- if not (source / 'plugin.yaml').is_file():
- raise ValueError('缺少随附的工具桥插件')
- for profile in dict.fromkeys(args.profile or ['default']):
- print(json.dumps(install(args.hermes_home.expanduser().resolve(), profile, source), ensure_ascii=False))
- if __name__ == '__main__':
- main()
|