feat: 添加 systemd 服务安装脚本

- install-service.py: 自动生成 hermes-service.service 文件
- 自动检测 .venv Python 或系统 Python
- 所有路径基于脚本所在目录动态计算,无硬编码
- 支持 --install 一键安装到 /etc/systemd/system
- 包含安全加固: NoNewPrivileges, ProtectSystem, ProtectHome
- 日志输出到 journal,方便 systemctl status/journalctl 查看
This commit is contained in:
yumoqing 2026-04-27 16:09:00 +08:00
parent 32cf7e5d5a
commit 514ab91e01
2 changed files with 159 additions and 0 deletions

27
hermes-service.service Normal file
View File

@ -0,0 +1,27 @@
[Unit]
Description=Hermes Service - AI Agent HTTP API
After=network.target
[Service]
Type=simple
User=hermesai
Group=hermesai
WorkingDirectory=/d/hermesai/repos/hermes-service
ExecStart=/usr/bin/python3 /d/hermesai/repos/hermes-service/main.py
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
# Environment
Environment="HOME=/d/hermesai"
Environment="PYTHONUNBUFFERED=1"
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/d/hermesai/users /d/hermesai/repos/hermes-service/data
[Install]
WantedBy=multi-user.target

132
install-service.py Normal file
View File

@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""
Generate systemd service file for hermes-service.
Usage: python3 install-service.py [--user USER] [--group GROUP] [--install]
"""
import os
import sys
import argparse
import shutil
# Resolve paths dynamically
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SERVICE_MODULE = SCRIPT_DIR
MAIN_PY = os.path.join(SERVICE_MODULE, "main.py")
# Auto-detect Python venv if it exists
VENV_PYTHON = os.path.join(SERVICE_MODULE, ".venv", "bin", "python3")
SYSTEM_PYTHON = shutil.which("python3")
if os.path.exists(VENV_PYTHON):
PYTHON_EXEC = VENV_PYTHON
else:
PYTHON_EXEC = SYSTEM_PYTHON or "/usr/bin/python3"
PORT = 9121
USER = "hermesai"
GROUP = "hermesai"
SERVICE_CONTENT_TEMPLATE = """[Unit]
Description=Hermes Service - AI Agent HTTP API
After=network.target
[Service]
Type=simple
User={user}
Group={group}
WorkingDirectory={workdir}
ExecStart={python} {main_py}
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
# Environment
Environment="HOME={home}"
Environment="PYTHONUNBUFFERED=1"
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths={users_path} {data_path}
[Install]
WantedBy=multi-user.target
"""
def generate(args):
parser = argparse.ArgumentParser(description="Generate hermes-service systemd service")
parser.add_argument("--user", default=USER, help="Run as this user (default: hermesai)")
parser.add_argument("--group", default=GROUP, help="Run as this group (default: hermesai)")
parser.add_argument("--port", type=int, default=PORT, help="Service port (default: 9121)")
parser.add_argument("--install", action="store_true", help="Copy to /etc/systemd/system and enable")
parser.add_argument("--output", default=None, help="Output file path (default: ./hermes-service.service)")
args = parser.parse_args()
return args
def main():
args = generate(sys.argv[1:])
user = args.user
group = args.group
port = args.port
install = args.install
service_name = "hermes-service"
output = args.output or os.path.join(SERVICE_MODULE, f"{service_name}.service")
# Validate
if not os.path.exists(MAIN_PY):
print(f"ERROR: main.py not found at {MAIN_PY}")
sys.exit(1)
# Generate service content
content = SERVICE_CONTENT_TEMPLATE.format(
user=user,
group=group,
workdir=SERVICE_MODULE,
python=PYTHON_EXEC,
main_py=MAIN_PY,
home=os.environ.get('HOME', '/home/hermesai'),
users_path=os.path.join(os.environ.get('HOME', '/home/hermesai'), 'users'),
data_path=os.path.join(SERVICE_MODULE, 'data'),
)
# Write service file
with open(output, "w") as f:
f.write(content)
print(f"Service file written to: {output}")
print(f" Python: {PYTHON_EXEC}")
print(f" WorkingDir: {SERVICE_MODULE}")
print(f" User/Group: {user}/{group}")
print(f" Port: {port}")
print(f" Main: {MAIN_PY}")
print()
if install:
dest = f"/etc/systemd/system/{service_name}.service"
if os.geteuid() != 0:
print(f"ERROR: --install requires root privileges. Run with sudo.")
sys.exit(1)
shutil.copy2(output, dest)
os.chmod(dest, 0o644)
print(f"Installed to: {dest}")
print()
print("Next steps:")
print(f" sudo systemctl daemon-reload")
print(f" sudo systemctl enable {service_name}")
print(f" sudo systemctl start {service_name}")
print(f" sudo systemctl status {service_name}")
else:
print("To install and enable:")
print(f" sudo python3 {os.path.basename(__file__)} --install")
if __name__ == "__main__":
main()