184 lines
6.5 KiB
Python
Executable File
184 lines
6.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
TFM aInventory - Systemd Service Installer (v1.12.0)
|
|
Converted from install_service.sh to Python per Decision D-05.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import yaml
|
|
import argparse
|
|
import subprocess
|
|
import logging
|
|
import pwd
|
|
import grp
|
|
from typing import Dict, Any
|
|
|
|
# Color codes
|
|
class Colors:
|
|
RED = '\033[0;31m'
|
|
GREEN = '\033[0;32m'
|
|
YELLOW = '\033[1;33m'
|
|
BLUE = '\033[0;34m'
|
|
NC = '\033[0m'
|
|
|
|
# Setup logging
|
|
logging.basicConfig(level=logging.INFO, format=f"{Colors.BLUE}[INFO]{Colors.NC} %(message)s")
|
|
logger = logging.getLogger("install_service")
|
|
|
|
SERVICE_NAME = "ainventory.service"
|
|
SERVICE_PATH = f"/etc/systemd/system/{SERVICE_NAME}"
|
|
|
|
# Inline template if file missing
|
|
DEFAULT_TEMPLATE = """[Unit]
|
|
Description=TFM aInventory Service
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User={SERVICE_USER}
|
|
Group={SERVICE_USER}
|
|
WorkingDirectory={PROJECT_DIR}
|
|
Environment=PATH={PROJECT_DIR}/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
|
|
ExecStart={PYTHON_PATH} {PROJECT_DIR}/scripts/run_standalone.py --backend-only
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
"""
|
|
|
|
def load_yaml(file_path: str) -> Dict[str, Any]:
|
|
if not os.path.exists(file_path):
|
|
return {}
|
|
try:
|
|
with open(file_path, 'r') as f:
|
|
return yaml.safe_load(f) or {}
|
|
except Exception as e:
|
|
logger.error(f"Failed to parse {file_path}: {e}")
|
|
return {}
|
|
|
|
def check_user_exists(username: str) -> bool:
|
|
try:
|
|
pwd.getpwnam(username)
|
|
return True
|
|
except KeyError:
|
|
return False
|
|
|
|
def main():
|
|
if os.getuid() != 0:
|
|
logger.error("This script must be run as root (use sudo)")
|
|
sys.exit(1)
|
|
|
|
parser = argparse.ArgumentParser(description="TFM aInventory Systemd Service Installer")
|
|
parser.add_argument("--user", default="www-data", help="Service user (default: www-data)")
|
|
parser.add_argument("--force", action="store_true", help="Overwrite existing service file")
|
|
|
|
args = parser.parse_args()
|
|
|
|
project_dir = os.getcwd()
|
|
python_path = sys.executable
|
|
|
|
# Load configs
|
|
backend_cfg = load_yaml("config/backend.yaml")
|
|
network_cfg = load_yaml("config/network.yaml")
|
|
|
|
app_cfg = backend_cfg.get("application", {})
|
|
ports_cfg = network_cfg.get("ports", {})
|
|
|
|
backend_port = ports_cfg.get("backend_port", 8916)
|
|
data_dir = os.path.abspath(app_cfg.get("data_dir", "./data"))
|
|
logs_dir = os.path.abspath(app_cfg.get("logs_dir", "./logs"))
|
|
|
|
logger.info(f"Installing {SERVICE_NAME}...")
|
|
|
|
if not check_user_exists(args.user):
|
|
logger.warning(f"User '{args.user}' does not exist. Suggestion: sudo useradd -r -s /bin/false {args.user}")
|
|
# We'll continue but warn, as user might want to create it later (though service won't start)
|
|
|
|
if os.path.exists(SERVICE_PATH) and not args.force:
|
|
logger.error(f"Service file {SERVICE_PATH} already exists. Use --force to overwrite.")
|
|
sys.exit(1)
|
|
|
|
# Read template if exists
|
|
template_content = DEFAULT_TEMPLATE
|
|
if os.path.exists("inventory.service.template"):
|
|
try:
|
|
with open("inventory.service.template", "r") as f:
|
|
content = f.read()
|
|
if content.strip():
|
|
template_content = content
|
|
# If using old template with __WORKING_DIR__, replace it
|
|
template_content = template_content.replace("__WORKING_DIR__", "{PROJECT_DIR}")
|
|
except Exception as e:
|
|
logger.warning(f"Failed to read inventory.service.template: {e}. Using default.")
|
|
|
|
# Format template
|
|
# Note: We need to handle keys that might not be in the template but are in our variables
|
|
# The plan mentions {PROJECT_DIR}, {SERVICE_USER}, {BACKEND_PORT}, {DATA_DIR}, {LOGS_DIR}
|
|
try:
|
|
# We'll use a safer way to replace placeholders to avoid KeyError if template doesn't have all of them
|
|
service_content = template_content.format(
|
|
PROJECT_DIR=project_dir,
|
|
SERVICE_USER=args.user,
|
|
BACKEND_PORT=backend_port,
|
|
DATA_DIR=data_dir,
|
|
LOGS_DIR=logs_dir,
|
|
PYTHON_PATH=python_path
|
|
)
|
|
except KeyError as e:
|
|
# Fallback if template has unknown placeholders
|
|
logger.warning(f"Template contains unknown placeholder: {e}. Attempting simple replacement.")
|
|
service_content = template_content.replace("{PROJECT_DIR}", project_dir)\
|
|
.replace("{SERVICE_USER}", args.user)\
|
|
.replace("{BACKEND_PORT}", str(backend_port))\
|
|
.replace("{DATA_DIR}", data_dir)\
|
|
.replace("{LOGS_DIR}", logs_dir)\
|
|
.replace("{PYTHON_PATH}", python_path)
|
|
|
|
# Write service file
|
|
try:
|
|
with open(SERVICE_PATH, "w") as f:
|
|
f.write(service_content)
|
|
os.chmod(SERVICE_PATH, 0o644)
|
|
logger.info(f"✓ Service file created at {SERVICE_PATH}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to write service file: {e}")
|
|
sys.exit(1)
|
|
|
|
# Ensure data and logs directories exist and are owned by the service user
|
|
for d in [data_dir, logs_dir]:
|
|
os.makedirs(d, exist_ok=True)
|
|
try:
|
|
uid = pwd.getpwnam(args.user).pw_uid
|
|
gid = grp.getgrnam(args.user).gr_gid
|
|
os.chown(d, uid, gid)
|
|
# Also chown contents if any
|
|
for root, dirs, files in os.walk(d):
|
|
for momo in dirs: os.chown(os.path.join(root, momo), uid, gid)
|
|
for momo in files: os.chown(os.path.join(root, momo), uid, gid)
|
|
except Exception as e:
|
|
logger.warning(f"Could not set permissions for {d}: {e}")
|
|
|
|
# Reload systemd
|
|
try:
|
|
subprocess.run(["systemctl", "daemon-reload"], check=True)
|
|
subprocess.run(["systemctl", "enable", SERVICE_NAME], check=True)
|
|
logger.info(f"✓ {SERVICE_NAME} enabled")
|
|
except subprocess.CalledProcessError as e:
|
|
logger.error(f"Failed to enable service: {e}")
|
|
sys.exit(1)
|
|
|
|
print(f"\n{Colors.GREEN}🚀 TFM aInventory service installed successfully!{Colors.NC}")
|
|
print(f"Service user: {args.user}")
|
|
print(f"Project dir: {project_dir}")
|
|
print("\nNext steps:")
|
|
print(f" 👉 sudo systemctl start {SERVICE_NAME}")
|
|
print(f" 👉 sudo systemctl status {SERVICE_NAME}")
|
|
print(f" 👉 journalctl -u {SERVICE_NAME} -f")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|