- install-service.py: 自动生成 hermes-service.service 文件 - 自动检测 .venv Python 或系统 Python - 所有路径基于脚本所在目录动态计算,无硬编码 - 支持 --install 一键安装到 /etc/systemd/system - 包含安全加固: NoNewPrivileges, ProtectSystem, ProtectHome - 日志输出到 journal,方便 systemctl status/journalctl 查看
133 lines
3.7 KiB
Python
133 lines
3.7 KiB
Python
#!/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()
|