/systemd.py

# imports - standard imports
import getpass
import os

# imports - third partyimports
import click

# imports - module imports
import bench
from bench.app import use_rq
from bench.bench import Bench
from bench.config.common_site_config import (
	get_gunicorn_workers,
	update_config,
	get_default_max_requests,
	compute_max_requests_jitter,
)
from bench.utils import exec_cmd, which, get_bench_name


def generate_systemd_config(
	bench_path,
	user=None,
	yes=False,
	stop=False,
	create_symlinks=False,
	delete_symlinks=False,
):

	if not user:
		user = getpass.getuser()

	config = Bench(bench_path).conf

	bench_dir = os.path.abspath(bench_path)
	bench_name = get_bench_name(bench_path)

	if stop:
		exec_cmd(
			f"sudo systemctl stop -- $(systemctl show -p Requires {bench_name}.target | cut -d= -f2)"
		)
		return

	if create_symlinks:
		_create_symlinks(bench_path)
		return

	if delete_symlinks:
		_delete_symlinks(bench_path)
		return

	number_of_workers = config.get("background_workers") or 1
	background_workers = []
	for i in range(number_of_workers):
		background_workers.append(
			get_bench_name(bench_path) + "-frappe-default-worker@" + str(i + 1) + ".service"
		)

	for i in range(number_of_workers):
		background_workers.append(
			get_bench_name(bench_path) + "-frappe-short-worker@" + str(i + 1) + ".service"
		)

	for i in range(number_of_workers):
		background_workers.append(
			get_bench_name(bench_path) + "-frappe-long-worker@" + str(i + 1) + ".service"
		)

	web_worker_count = config.get(
		"gunicorn_workers", get_gunicorn_workers()["gunicorn_workers"]
	)
	max_requests = config.get(
		"gunicorn_max_requests", get_default_max_requests(web_worker_count)
	)

	bench_info = {
		"bench_dir": bench_dir,
		"sites_dir": os.path.join(bench_dir, "sites"),
		"user": user,
		"use_rq": use_rq(bench_path),
		"http_timeout": config.get("http_timeout", 120),
		"redis_server": which("redis-server"),
		"node": which("node") or which("nodejs"),
		"redis_cache_config": os.path.join(bench_dir, "config", "redis_cache.conf"),
		"redis_queue_config": os.path.join(bench_dir, "config", "redis_queue.conf"),
		"webserver_port": config.get("webserver_port", 8000),
		"gunicorn_workers": web_worker_count,
		"gunicorn_max_requests": max_requests,
		"gunicorn_max_requests_jitter": compute_max_requests_jitter(max_requests),
		"bench_name": get_bench_name(bench_path),
		"worker_target_wants": " ".join(background_workers),
		"bench_cmd": which("bench"),
	}

	if not yes:
		click.confirm(
			"current systemd configuration will be overwritten. Do you want to continue?",
			abort=True,
		)

	setup_systemd_directory(bench_path)
	setup_main_config(bench_info, bench_path)
	setup_workers_config(bench_info, bench_path)
	setup_web_config(bench_info, bench_path)
	setup_redis_config(bench_info, bench_path)

	update_config({"restart_systemd_on_update": False}, bench_path=bench_path)
	update_config({"restart_supervisor_on_update": False}, bench_path=bench_path)


def setup_systemd_directory(bench_path):
	if not os.path.exists(os.path.join(bench_path, "config", "systemd")):
		os.makedirs(os.path.join(bench_path, "config", "systemd"))


def setup_main_config(bench_info, bench_path):
	# Main config
	bench_template = bench.config.env().get_template("systemd/frappe-bench.target")
	bench_config = bench_template.render(**bench_info)
	bench_config_path = os.path.join(
		bench_path, "config", "systemd", bench_info.get("bench_name") + ".target"
	)

	with open(bench_config_path, "w") as f:
		f.write(bench_config)


def setup_workers_config(bench_info, bench_path):
	# Worker Group
	bench_workers_target_template = bench.config.env().get_template(
		"systemd/frappe-bench-workers.target"
	)
	bench_default_worker_template = bench.config.env().get_template(
		"systemd/frappe-bench-frappe-default-worker.service"
	)
	bench_short_worker_template = bench.config.env().get_template(
		"systemd/frappe-bench-frappe-short-worker.service"
	)
	bench_long_worker_template = bench.config.env().get_template(
		"systemd/frappe-bench-frappe-long-worker.service"
	)
	bench_schedule_worker_template = bench.config.env().get_template(
		"systemd/frappe-bench-frappe-schedule.service"
	)

	bench_workers_target_config = bench_workers_target_template.render(**bench_info)
	bench_default_worker_config = bench_default_worker_template.render(**bench_info)
	bench_short_worker_config = bench_short_worker_template.render(**bench_info)
	bench_long_worker_config = bench_long_worker_template.render(**bench_info)
	bench_schedule_worker_config = bench_schedule_worker_template.render(**bench_info)

	bench_workers_target_config_path = os.path.join(
		bench_path, "config", "systemd", bench_info.get("bench_name") + "-workers.target"
	)
	bench_default_worker_config_path = os.path.join(
		bench_path,
		"config",
		"systemd",
		bench_info.get("bench_name") + "[email protected]",
	)
	bench_short_worker_config_path = os.path.join(
		bench_path,
		"config",
		"systemd",
		bench_info.get("bench_name") + "[email protected]",
	)
	bench_long_worker_config_path = os.path.join(
		bench_path,
		"config",
		"systemd",
		bench_info.get("bench_name") + "[email protected]",
	)
	bench_schedule_worker_config_path = os.path.join(
		bench_path,
		"config",
		"systemd",
		bench_info.get("bench_name") + "-frappe-schedule.service",
	)

	with open(bench_workers_target_config_path, "w") as f:
		f.write(bench_workers_target_config)

	with open(bench_default_worker_config_path, "w") as f:
		f.write(bench_default_worker_config)

	with open(bench_short_worker_config_path, "w") as f:
		f.write(bench_short_worker_config)

	with open(bench_long_worker_config_path, "w") as f:
		f.write(bench_long_worker_config)

	with open(bench_schedule_worker_config_path, "w") as f:
		f.write(bench_schedule_worker_config)


def setup_web_config(bench_info, bench_path):
	# Web Group
	bench_web_target_template = bench.config.env().get_template(
		"systemd/frappe-bench-web.target"
	)
	bench_web_service_template = bench.config.env().get_template(
		"systemd/frappe-bench-frappe-web.service"
	)
	bench_node_socketio_template = bench.config.env().get_template(
		"systemd/frappe-bench-node-socketio.service"
	)

	bench_web_target_config = bench_web_target_template.render(**bench_info)
	bench_web_service_config = bench_web_service_template.render(**bench_info)
	bench_node_socketio_config = bench_node_socketio_template.render(**bench_info)

	bench_web_target_config_path = os.path.join(
		bench_path, "config", "systemd", bench_info.get("bench_name") + "-web.target"
	)
	bench_web_service_config_path = os.path.join(
		bench_path, "config", "systemd", bench_info.get("bench_name") + "-frappe-web.service"
	)
	bench_node_socketio_config_path = os.path.join(
		bench_path,
		"config",
		"systemd",
		bench_info.get("bench_name") + "-node-socketio.service",
	)

	with open(bench_web_target_config_path, "w") as f:
		f.write(bench_web_target_config)

	with open(bench_web_service_config_path, "w") as f:
		f.write(bench_web_service_config)

	with open(bench_node_socketio_config_path, "w") as f:
		f.write(bench_node_socketio_config)


def setup_redis_config(bench_info, bench_path):
	# Redis Group
	bench_redis_target_template = bench.config.env().get_template(
		"systemd/frappe-bench-redis.target"
	)
	bench_redis_cache_template = bench.config.env().get_template(
		"systemd/frappe-bench-redis-cache.service"
	)
	bench_redis_queue_template = bench.config.env().get_template(
		"systemd/frappe-bench-redis-queue.service"
	)

	bench_redis_target_config = bench_redis_target_template.render(**bench_info)
	bench_redis_cache_config = bench_redis_cache_template.render(**bench_info)
	bench_redis_queue_config = bench_redis_queue_template.render(**bench_info)

	bench_redis_target_config_path = os.path.join(
		bench_path, "config", "systemd", bench_info.get("bench_name") + "-redis.target"
	)
	bench_redis_cache_config_path = os.path.join(
		bench_path, "config", "systemd", bench_info.get("bench_name") + "-redis-cache.service"
	)
	bench_redis_queue_config_path = os.path.join(
		bench_path, "config", "systemd", bench_info.get("bench_name") + "-redis-queue.service"
	)

	with open(bench_redis_target_config_path, "w") as f:
		f.write(bench_redis_target_config)

	with open(bench_redis_cache_config_path, "w") as f:
		f.write(bench_redis_cache_config)

	with open(bench_redis_queue_config_path, "w") as f:
		f.write(bench_redis_queue_config)


def _create_symlinks(bench_path):
	bench_dir = os.path.abspath(bench_path)
	etc_systemd_system = os.path.join("/", "etc", "systemd", "system")
	config_path = os.path.join(bench_dir, "config", "systemd")
	unit_files = get_unit_files(bench_dir)
	for unit_file in unit_files:
		filename = "".join(unit_file)
		exec_cmd(
			f'sudo ln -s {config_path}/{filename} {etc_systemd_system}/{"".join(unit_file)}'
		)
	exec_cmd("sudo systemctl daemon-reload")


def _delete_symlinks(bench_path):
	bench_dir = os.path.abspath(bench_path)
	etc_systemd_system = os.path.join("/", "etc", "systemd", "system")
	unit_files = get_unit_files(bench_dir)
	for unit_file in unit_files:
		exec_cmd(f'sudo rm {etc_systemd_system}/{"".join(unit_file)}')
	exec_cmd("sudo systemctl daemon-reload")


def get_unit_files(bench_path):
	bench_name = get_bench_name(bench_path)
	unit_files = [
		[bench_name, ".target"],
		[bench_name + "-workers", ".target"],
		[bench_name + "-web", ".target"],
		[bench_name + "-redis", ".target"],
		[bench_name + "-frappe-default-worker@", ".service"],
		[bench_name + "-frappe-short-worker@", ".service"],
		[bench_name + "-frappe-long-worker@", ".service"],
		[bench_name + "-frappe-schedule", ".service"],
		[bench_name + "-frappe-web", ".service"],
		[bench_name + "-node-socketio", ".service"],
		[bench_name + "-redis-cache", ".service"],
		[bench_name + "-redis-queue", ".service"],
	]
	return unit_files