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:
parent
32cf7e5d5a
commit
514ab91e01
27
hermes-service.service
Normal file
27
hermes-service.service
Normal 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
132
install-service.py
Normal 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()
|
||||
Loading…
x
Reference in New Issue
Block a user