#!/bin/sh
#
# nexor-setup — связать панель и агента на одной машине.
#
# Панель и агент ставятся двумя пакетами, и до сих пор их знакомили руками:
# взять секрет из файла агента, вписать в файл панели, выбрать каталоги,
# включить службу. Четыре шага, каждый из которых забывается, — и панель
# говорит «агент не настроен» на файлах, логах и сайтах. На одной машине это
# лишняя работа: оба файла лежат на одном диске и пишет их один root.
#
# Скрипт зовут postinst обоих пакетов, в любом порядке: кто встал вторым, тот
# и свяжет. Он идемпотентен и НИЧЕГО НЕ ПЕРЕЗАПИСЫВАЕТ — значение, которое
# уже задано, остаётся как есть. Настройка, сделанная руками, важнее
# догадки, и молча заменить её значило бы сломать машину, которую настроили
# не по умолчанию.
#
# NEXOR_SETUP_ROOT переставляет корень — так его проверяют, не трогая машину.
set -e

ROOT="${NEXOR_SETUP_ROOT:-}"
ETC="$ROOT/etc/nexorpanel"
PANEL="$ETC/nexor-api.env"
AGENT="$ETC/nexor-agent.env"

say() { echo "nexor-setup: $*"; }

changed=0

# value — значение строки KEY=..., если она есть и не закомментирована.
value() {
	[ -f "$1" ] || return 0
	sed -n "s/^$2=//p" "$1" | head -n 1
}

# blank — значения нет или оно пустое.
blank() {
	[ -z "$(value "$1" "$2")" ]
}

# put — записать значение.
#
# Закомментированная строка заменяется на месте: объяснение над ней остаётся
# там же, где было, и файл читается дальше как сплошной текст, а не как
# список умолчаний с приписками в конце.
put() {
	file=$1 key=$2 val=$3
	if grep -q "^$key=" "$file"; then
		awk -v k="$key" -v v="$val" \
			'$0 ~ "^" k "=" && !done { print k "=" v; done=1; next } { print }' \
			"$file" > "$file.tmp"
	elif grep -q "^#$key=" "$file"; then
		awk -v k="$key" -v v="$val" \
			'$0 ~ "^#" k "=" && !done { print k "=" v; done=1; next } { print }' \
			"$file" > "$file.tmp"
	else
		cp "$file" "$file.tmp"
		printf '%s=%s\n' "$key" "$val" >> "$file.tmp"
	fi
	cmp -s "$file.tmp" "$file" || changed=1
	cat "$file.tmp" > "$file"
	rm -f "$file.tmp"
}

# Нечего связывать, пока не стоят оба: это не ошибка, а ещё не наступивший
# случай. Пакет, вставший вторым, позовёт этот же скрипт снова.
[ -f "$PANEL" ] || exit 0

# Одной проверкой, а не двумя: у ненайденного файла и у файла без секрета
# ответ один — связывать нечем. Отдельная проверка на существование файла
# была бы веткой, которую ничем не отличить от этой.
secret=$(value "$AGENT" NEXOR_AGENT_SECRET)
if [ -z "$secret" ]; then
	[ -f "$AGENT" ] && say "у агента нет секрета — связывать нечем; см. deploy/README.md"
	exit 0
fi

# ---------------------------------------------------------------- агент

# Каталоги агента заполняются только те, что пусты.
#
# Выбор здесь — не догадка про чужую машину, а собственный дом панели:
# /var/www/vhosts она заводит сама, и до неё там ничего нет. Каталог с чужим
# содержимым так не занимают: если он уже есть и не пуст, строка остаётся
# пустой, и агент честно скажет, что сайтами эта машина не занимается.
sites="$ROOT/var/www/vhosts"
if blank "$AGENT" NEXOR_AGENT_SITE_ROOT; then
	if [ ! -e "$sites" ] || [ -z "$(ls -A "$sites" 2>/dev/null)" ]; then
		mkdir -p "$sites"
		chmod 755 "$sites"
		put "$AGENT" NEXOR_AGENT_SITE_ROOT /var/www/vhosts
		say "сайты будут в /var/www/vhosts"
	else
		say "/var/www/vhosts занят — каталог сайтов оставлен незаданным"
	fi
fi

# Конфигурация веб-сервера — туда, где её держит эта сборка nginx. Каталога
# нет — значит, nginx не установлен, и придумывать ему каталог незачем.
if blank "$AGENT" NEXOR_AGENT_VHOST_DIR; then
	for dir in /etc/nginx/conf.d /etc/nginx/sites-enabled; do
		if [ -d "$ROOT$dir" ]; then
			put "$AGENT" NEXOR_AGENT_VHOST_DIR "$dir"
			say "конфигурация сайтов будет в $dir"
			break
		fi
	done
fi

# Файловый менеджер видит ровно каталог сайтов и ничего больше.
if blank "$AGENT" NEXOR_AGENT_ROOTS; then
	root=$(value "$AGENT" NEXOR_AGENT_SITE_ROOT)
	[ -n "$root" ] && put "$AGENT" NEXOR_AGENT_ROOTS "$root"
fi

# Каталог копий.
#
# Без него агент копий не делает, расписание на карточке сайта включить
# нельзя, и у каждого сайта навсегда висит «Резервные копии не настроены» —
# предупреждение, с которым человеку нечего сделать.
#
# Обязан лежать ВНЕ каталога сайтов: копия внутри сайта попадает в следующую
# копию, растёт вдвое с каждым разом и исчезает вместе с тем, от чего должна
# была спасти. Агент такую настройку и не примет.
if blank "$AGENT" NEXOR_AGENT_BACKUP_DIR; then
	backups="$ROOT/var/backups/nexorpanel"
	mkdir -p "$backups"
	chmod 700 "$backups"
	put "$AGENT" NEXOR_AGENT_BACKUP_DIR /var/backups/nexorpanel
	say "копии сайтов будут в /var/backups/nexorpanel"
fi
# Снимки главных страниц делает служба снимков, а не эта машина.
#
# Браузер здесь был дорогим гостем: его надо поставить, держать обновлённым,
# запускать от отдельного пользователя — он открывает враждебный по
# определению HTML — и мириться с выключенной песочницей. Теперь машине нужен
# только выход наружу к одному адресу, и он записан умолчанием в самом агенте:
# писать сюда нечего, пока его не меняют.

# Где снимки лежат. Отдельным каталогом, а не во временном: временный чистят
# при перезагрузке, и каждый сайт после неё снимается заново — два десятка
# запущенных браузеров на первое же открытие списка.
if blank "$AGENT" NEXOR_AGENT_SHOT_DIR; then
	shots="$ROOT/var/cache/nexorpanel/shots"
	mkdir -p "$shots"
	chmod 755 "$shots"
	put "$AGENT" NEXOR_AGENT_SHOT_DIR /var/cache/nexorpanel/shots
fi

# Логи веб-сервера — отдельным списком и только на чтение.
if blank "$AGENT" NEXOR_AGENT_LOG_ROOTS && [ -d "$ROOT/var/log/nginx" ]; then
	put "$AGENT" NEXOR_AGENT_LOG_ROOTS /var/log/nginx
	say "логи веб-сервера будут читаться из /var/log/nginx"
fi

if blank "$AGENT" NEXOR_AGENT_WEB_SERVICE && [ -n "$(value "$AGENT" NEXOR_AGENT_VHOST_DIR)" ]; then
	put "$AGENT" NEXOR_AGENT_WEB_SERVICE nginx
fi

# Имя пользователя веб-сервера разное у разных сборок: www-data у Debian,
# nginx у Rocky. Спрашиваем систему, а не угадываем.
if blank "$AGENT" NEXOR_AGENT_WEB_USER; then
	for user in ${NEXOR_SETUP_WEB_USER:-www-data nginx}; do
		if getent passwd "$user" >/dev/null 2>&1; then
			put "$AGENT" NEXOR_AGENT_WEB_USER "$user"
			break
		fi
	done
fi

# Каталог сборки сайтов.
#
# Сайт, который собирает фронтенд у себя (npm ci + vite build по вебхуку),
# держал node_modules и кэш npm в своём каталоге: полгигабайта в размере
# сайта, в каждой его копии и в SFTP. Пул PHP получает <каталог>/<домен> как
# BUILD_DIR и npm_config_cache, и сборка идёт там.
if blank "$AGENT" NEXOR_AGENT_BUILD_ROOT; then
	builds="$ROOT/var/lib/site-builds"
	mkdir -p "$builds"
	chmod 755 "$builds"
	put "$AGENT" NEXOR_AGENT_BUILD_ROOT /var/lib/site-builds
	say "сборка сайтов (node_modules, кэш npm) будет в /var/lib/site-builds"
fi

# has — есть ли программа на этой машине (под подставным корнем тоже).
has() {
	for dir in /usr/bin /usr/sbin /bin /sbin; do
		[ -x "$ROOT$dir/$1" ] && return 0
	done
	return 1
}

# unit — первый из названных unit'ов, который на машине есть.
unit() {
	for name in "$@"; do
		for dir in /lib/systemd/system /usr/lib/systemd/system /etc/systemd/system; do
			[ -f "$ROOT$dir/$name" ] && { echo "$name"; return 0; }
		done
	done
	return 1
}

# service — дописать службу в NEXOR_AGENT_SERVICES, если ключа там нет.
# Дописывается, а не переписывается: чужие ключи остаются как были.
service() {
	cur=$(value "$AGENT" NEXOR_AGENT_SERVICES)
	case ",$cur," in
		*",$1="*) return 0 ;;
	esac
	if [ -n "$cur" ]; then
		put "$AGENT" NEXOR_AGENT_SERVICES "$cur,$1=$2"
	else
		put "$AGENT" NEXOR_AGENT_SERVICES "$1=$2"
	fi
}

# Модули включаются сами там, где для них всё есть.
#
# Раньше каждый включали руками, и панель на свежей машине отвечала «агенту
# не поручена эта часть работы» на расписании, доступах и зонах — хотя cron,
# sshd и сервер имён уже стояли. Строка, заданная руками, остаётся как есть:
# NEXOR_AGENT_CRON=0 (и так же у остальных) — отказ, и его здесь не трогают.

# Расписание: задания выполняются от пользователя сайта, не от root.
if blank "$AGENT" NEXOR_AGENT_CRON && has crontab; then
	put "$AGENT" NEXOR_AGENT_CRON 1
	say "расписание сайтов включено"
fi

# Терминала здесь нет, и это не пропуск.
#
# NEXOR_AGENT_CONSOLE открывает из панели командную строку машины от имени
# агента, то есть от root, и отменяет правило, на котором стоит весь агент:
# выполняется не присланное, а выбранное из списка. Такое не включают за
# человека — ни «если на машине есть bash», ни по какому другому признаку:
# машина, обновившая агента, корневой оболочки из браузера получить не должна.
# Строку дописывает администратор машины сам; как и зачем — в deploy/README.md.

# Доступы к файлам сайта (SFTP): своё правило в каталоге sshd, проверка
# sshd -t перед перечитыванием.
if blank "$AGENT" NEXOR_AGENT_SHELL && [ -d "$ROOT/etc/ssh/sshd_config.d" ]; then
	put "$AGENT" NEXOR_AGENT_SHELL 1
	blank "$AGENT" NEXOR_AGENT_SSHD_DIR && put "$AGENT" NEXOR_AGENT_SSHD_DIR /etc/ssh/sshd_config.d
	if blank "$AGENT" NEXOR_AGENT_SSH_SERVICE && ssh_unit=$(unit ssh.service sshd.service); then
		service ssh "$ssh_unit"
		put "$AGENT" NEXOR_AGENT_SSH_SERVICE ssh
	fi
	say "доступы к файлам сайтов (SFTP) включены"
fi

# Зоны DNS — только если сервер имён стоит: без него зоны писались бы в
# каталог, который никто не читает.
if blank "$AGENT" NEXOR_AGENT_DNS && has named && dns_unit=$(unit named.service bind9.service); then
	dns="$ROOT/etc/nexorpanel/dns"
	mkdir -p "$dns"
	[ -f "$dns/zones.conf" ] || : > "$dns/zones.conf"
	chmod 644 "$dns/zones.conf"
	# сервер имён читает каталог от своего пользователя
	if getent group bind >/dev/null 2>&1 && [ -z "$ROOT" ]; then
		chgrp bind "$dns" && chmod 750 "$dns"
	else
		chmod 755 "$dns"
	fi
	for conf in /etc/bind/named.conf /etc/named.conf; do
		if [ -f "$ROOT$conf" ]; then
			grep -q '/etc/nexorpanel/dns/zones.conf' "$ROOT$conf" || \
				printf 'include "/etc/nexorpanel/dns/zones.conf";\n' >> "$ROOT$conf"
			break
		fi
	done
	service named "$dns_unit"
	put "$AGENT" NEXOR_AGENT_DNS 1
	blank "$AGENT" NEXOR_AGENT_DNS_DIR && put "$AGENT" NEXOR_AGENT_DNS_DIR /etc/nexorpanel/dns
	blank "$AGENT" NEXOR_AGENT_DNS_SERVICE && put "$AGENT" NEXOR_AGENT_DNS_SERVICE named
	say "зоны DNS включены"
fi

# Каталог своих правил сайтов: <каталог>/<домен>.conf подключается вместо
# «location /» панели — туда переносят .htaccess. Только root: правила
# веб-сервера открывают всю машину.
if [ -n "$(value "$AGENT" NEXOR_AGENT_VHOST_DIR)" ]; then
	rules=$(value "$AGENT" NEXOR_AGENT_RULES_DIR)
	[ -n "$rules" ] || rules=/etc/nginx/nexor-rules
	if [ ! -d "$ROOT$rules" ]; then
		mkdir -p "$ROOT$rules"
		chmod 755 "$ROOT$rules"
		say "свои правила сайтов — в $rules/<домен>.conf"
	fi
fi

# Базы данных: своя учётная запись агента в MariaDB/MySQL.
#
# Без NEXOR_AGENT_DB_DSN панель не заводит ни баз, ни их пользователей, и в
# копиях сайтов нет дампов. Пароль root'а СУБД в файл не кладём: на Debian
# root входит по сокету без пароля, и через этот вход заводится отдельная
# учётная запись nexor_agent со случайным паролем — её можно отозвать, не
# трогая root. Права полные и с GRANT OPTION: без них агент не заведёт базу
# и не выдаст на неё права её пользователю. Только на живой машине и только
# если вход по сокету есть; строку «NEXOR_AGENT_DB_DSN=», вписанную руками
# пустой, не трогаем — это отказ.
if [ -z "$ROOT" ] && ! grep -q '^NEXOR_AGENT_DB_DSN=' "$AGENT"; then
	sock=""
	for s in /run/mysqld/mysqld.sock /var/run/mysqld/mysqld.sock /var/lib/mysql/mysql.sock; do
		[ -S "$s" ] && { sock=$s; break; }
	done
	client=""
	for c in mariadb mysql; do
		command -v "$c" >/dev/null 2>&1 && { client=$c; break; }
	done
	if [ -n "$sock" ] && [ -n "$client" ] && "$client" --protocol=socket -S "$sock" -e 'SELECT 1' >/dev/null 2>&1; then
		pass=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n')
		if "$client" --protocol=socket -S "$sock" -e "
			CREATE USER IF NOT EXISTS 'nexor_agent'@'localhost' IDENTIFIED BY '$pass';
			ALTER USER 'nexor_agent'@'localhost' IDENTIFIED BY '$pass';
			GRANT ALL PRIVILEGES ON *.* TO 'nexor_agent'@'localhost' WITH GRANT OPTION;
			FLUSH PRIVILEGES;" >/dev/null 2>&1; then
			put "$AGENT" NEXOR_AGENT_DB_DSN "nexor_agent:$pass@unix($sock)/"
			say "базы данных: агент входит в СУБД как nexor_agent"
		else
			say "не удалось завести nexor_agent в СУБД — задайте NEXOR_AGENT_DB_DSN руками"
		fi
	fi
fi

# PHP-FPM — каждая установленная версия.
#
# Без этого списка агент не знает ни одной версии, панель молча сохраняет сайт
# «без PHP», и вместо страниц отдаются исходники .php. Версия берётся, только
# если есть и каталог пулов, и её служба: пул, который некому перечитать, —
# настройка, которая не применится.
if blank "$AGENT" NEXOR_AGENT_PHP; then
	php=""
	for pool in "$ROOT"/etc/php/*/fpm/pool.d; do
		[ -d "$pool" ] || continue
		ver=${pool#"$ROOT"/etc/php/}
		ver=${ver%%/*}
		fpm=$(unit "php$ver-fpm.service") || continue
		service "php$ver-fpm" "$fpm"
		php="$php,$ver=/etc/php/$ver/fpm/pool.d=php$ver-fpm"
	done
	if [ -n "$php" ]; then
		put "$AGENT" NEXOR_AGENT_PHP "${php#,}"
		say "PHP для сайтов: ${php#,}"
	fi
fi
# Каталог сокетов пулов — в /run, и после перезагрузки его нет: php-fpm, не
# найдя его, не поднимет ни одного пула. tmpfiles заводит его при загрузке.
if [ -n "$(value "$AGENT" NEXOR_AGENT_PHP)" ]; then
	blank "$AGENT" NEXOR_AGENT_PHP_SOCKETS && put "$AGENT" NEXOR_AGENT_PHP_SOCKETS /run/nexor-php
	sockets=$(value "$AGENT" NEXOR_AGENT_PHP_SOCKETS)
	tmpfiles="$ROOT/etc/tmpfiles.d/nexor-php.conf"
	if [ ! -f "$tmpfiles" ]; then
		mkdir -p "$(dirname "$tmpfiles")"
		printf 'd %s 0755 root root -\n' "$sockets" > "$tmpfiles"
	fi
	if [ -z "$ROOT" ]; then mkdir -p "$sockets"; fi
fi

# Каталог проверки для сертификата машины.
#
# Сертификат на имя самой машины (для почты) выпускается без сайта: файл
# проверки лежит в своём каталоге агента, и отдаёт его vhost агента на :80.
# Каталог должен читаться веб-сервером, поэтому не внутри каталога
# сертификатов (тот 0700), и заводится здесь: /var/lib агенту закрыт
# песочницей, и сам он его не создаст.
acme=""
if [ -n "$(value "$AGENT" NEXOR_AGENT_CERT_DIR)" ]; then
	acme=$(value "$AGENT" NEXOR_AGENT_ACME_ROOT)
	[ -n "$acme" ] || acme=/var/lib/nexor-agent/acme
	mkdir -p "$ROOT$acme"
	chmod 755 "$ROOT$acme"
fi

# Куда агенту можно писать.
#
# Unit агента запирает систему целиком (ProtectSystem=strict), а работа его —
# писать: каталоги сайтов, пулы и vhost'ы, учётные записи (useradd пишет
# /etc/passwd), расписание, копии, сборку. Без этого файла любая из этих
# правок падает с «read-only file system». Файл свой и переписывается целиком;
# чужой файл с тем же именем не трогается. Пути с «-» впереди systemd
# пропускает, если их нет.
dropin="$ROOT/etc/systemd/system/nexor-agent.service.d/nexor-setup.conf"
if [ ! -f "$dropin" ] || grep -q '^# Создано nexor-setup' "$dropin"; then
	paths=""
	for key in NEXOR_AGENT_SITE_ROOT NEXOR_AGENT_BACKUP_DIR NEXOR_AGENT_SHOT_DIR \
		NEXOR_AGENT_BUILD_ROOT NEXOR_AGENT_CERT_DIR NEXOR_AGENT_PHP_SOCKETS \
		NEXOR_AGENT_MAIL_HOME NEXOR_AGENT_MAIL_MAPS; do
		v=$(value "$AGENT" "$key")
		[ -n "$v" ] && paths="$paths -$v"
	done
	[ -n "$(value "$AGENT" NEXOR_AGENT_SITE_ROOT)" ] && paths="$paths -/etc"
	# vhost сертификата машины ложится в NEXOR_AGENT_VHOST_DIR, то есть под
	# /etc выше; сам сертификат — в NEXOR_AGENT_CERT_DIR, он уже в списке
	[ -n "$acme" ] && paths="$paths -$acme"
	[ "$(value "$AGENT" NEXOR_AGENT_CRON)" = 1 ] && paths="$paths -/var/spool/cron"
	mkdir -p "$(dirname "$dropin")"
	{
		echo "# Создано nexor-setup. Переписывается при каждом запуске."
		echo "[Service]"
		echo "ReadWritePaths=${paths# }"
		# Список сетевых интерфейсов (раздел «IP-адреса», выбор адреса сайта)
		# ядро отдаёт только через netlink: без AF_NETLINK агент молча видел
		# ноль адресов. Строка добавляется к списку из unit'а, а не заменяет его.
		echo "RestrictAddressFamilies=AF_NETLINK"
	} > "$dropin.tmp"
	if ! cmp -s "$dropin.tmp" "$dropin"; then
		mv "$dropin.tmp" "$dropin"
		changed=1
		if [ -z "$ROOT" ] && [ -d /run/systemd/system ]; then
			systemctl daemon-reload >/dev/null 2>&1 || true
		fi
	else
		rm -f "$dropin.tmp"
	fi
fi

# ---------------------------------------------------------------- панель

# Адрес берётся из того, на чём агент слушает: 127.0.0.1 по умолчанию, и
# менять это не нам. Без TLS — потому что это та же машина: соединение не
# выходит за её пределы, и шифровать его не от кого.
listen=$(value "$AGENT" NEXOR_AGENT_LISTEN)
[ -n "$listen" ] || listen=127.0.0.1:8462

if blank "$PANEL" NEXOR_AGENTS; then
	put "$PANEL" NEXOR_AGENTS "1=http://$listen"
	say "панель будет искать агента на http://$listen"
fi
if blank "$PANEL" NEXOR_AGENT_SECRET; then
	put "$PANEL" NEXOR_AGENT_SECRET "$secret"
	say "секрет агента вписан в настройки панели"
fi

# ---------------------------------------------------------------- служба

# Агент поднимается сразу: он слушает только эту машину, и оставлять его
# выключенным значит оставить панель говорить «агент не настроен» при
# настроенном агенте.
# Веб-сервер — в группу каждого сайта.
#
# Каталог сайта закрыт от соседей (0750, группа сайта), и веб-сервер, которого
# в группу не взяли, получает «Permission denied»: вместо страниц — 404, а
# Let's Encrypt не находит файл проверки, и сертификат не выпускается. Новые
# сайты агент открывает сам, здесь — те, что завели раньше. Только на живой
# машине: под подставным корнем usermod тронул бы настоящую.
web_user=$(value "$AGENT" NEXOR_AGENT_WEB_USER)
sites=$(value "$AGENT" NEXOR_AGENT_SITE_ROOT)
if [ -z "$ROOT" ] && [ -n "$web_user" ] && [ "$web_user" != root ] && \
	[ -n "$sites" ] && [ -d "$sites" ] && id "$web_user" >/dev/null 2>&1; then
	joined=0
	for dir in "$sites"/*/; do
		[ -d "$dir" ] || continue
		group=$(stat -c %G "$dir")
		case "$group" in root|UNKNOWN|"") continue ;; esac
		id -nG "$web_user" | tr ' ' '\n' | grep -qx "$group" && continue
		if usermod --append --groups "$group" "$web_user"; then
			joined=1
			say "$web_user добавлен в группу сайта $group"
		fi
	done
	# группы рабочие процессы получают при запуске: перезагрузка их и меняет
	web_unit=$(value "$AGENT" NEXOR_AGENT_WEB_SERVICE)
	if [ "$joined" = 1 ] && [ -n "$web_unit" ] && [ -d /run/systemd/system ]; then
		systemctl reload "$web_unit" >/dev/null 2>&1 || \
			say "не удалось перезагрузить $web_unit — сделайте это руками"
	fi
fi

# ------------------------------------------------------------ тайный вход
#
# Панель, открытая по известному адресу, подбирается круглосуточно: сканер
# находит форму входа по разметке, и дальше остаётся перебирать пароли.
# Поэтому у установки может быть своя случайная строка в адресе — «/8a66bdca/»,
# — под которой живёт всё, а мимо приходящие видят обычную пустую страницу.
#
# Заводится она ТОЛЬКО на свежей машине: там, где панель ещё ни разу не
# запускалась, её адрес никто не записал и никому не прислал. На работающей
# установке это была бы смена адреса без спроса — закладки администратора
# перестали бы открываться, и вместо панели он увидел бы ту самую пустую
# страницу. Поэтому там строка только предлагается, а вписывает её человек.
if blank "$PANEL" NEXOR_ENTRANCE; then
	data=$(value "$PANEL" NEXOR_DATA)
	[ -n "$data" ] || data=/var/lib/nexorpanel/panel.db
	entrance=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n')
	port=$((10000 + $(head -c 2 /dev/urandom | od -An -tu2 | tr -d ' \n') % 50000))
	if [ ! -s "$ROOT$data" ]; then
		put "$PANEL" NEXOR_ENTRANCE "$entrance"
		say "тайный вход панели: /$entrance/ — по нему она и открывается"
		say "порт для неё возьмите свой, например $port: панель за nginx, и порт — его дело"
		say "готовый кусок конфигурации: $ETC/nginx-panel.conf.example"
	else
		say "тайный вход не настроен. Чтобы закрыть панель своей строкой, впишите"
		say "  NEXOR_ENTRANCE=$entrance   в $PANEL"
		say "и перезапустите nexor-api; адрес станет https://<имя>:<порт>/$entrance/"
	fi
fi

# Кусок конфигурации nginx — файлом рядом с настройками, а не правкой живого
# веб-сервера.
#
# Веб-сервер на этой машине обслуживает чужие сайты. Скрипт, который правит
# его конфигурацию сам, однажды напишет туда строку с путём к сертификату,
# которого нет, — и nginx не перезапустится вместе со всеми сайтами разом.
# Поэтому здесь только пример с подставленными значениями: его читают,
# правят имя и копируют.
entrance=$(value "$PANEL" NEXOR_ENTRANCE)
example="$ETC/nginx-panel.conf.example"
if [ -n "$entrance" ] && [ ! -f "$example" ]; then
	addr=$(value "$PANEL" NEXOR_ADDR)
	[ -n "$addr" ] || addr=127.0.0.1:8080
	port=$((10000 + $(head -c 2 /dev/urandom | od -An -tu2 | tr -d ' \n') % 50000))
	cat > "$example" <<NGINX
# Панель на своём порту и за своей строкой в адресе.
#
# Порт здесь выбран случайно при установке — его можно поменять на любой
# свободный. Смысл не в том, что порт секретный (его видно сканером), а в том,
# что на 80 и 443 панели нет вовсе: там сайты клиентов.
#
#   https://<имя машины>:$port/$entrance/
#
# Строку «$entrance» знает панель: всё, что приходит мимо неё, получает пустую
# страницу 404. Менять её здесь не нужно — она берётся из NEXOR_ENTRANCE.
server {
    listen $port ssl;
    http2 on;
    server_name _;

    # сертификат машины: его выпускает сама панель (Серверы → сертификат
    # машины), либо подставьте свой
    ssl_certificate     /etc/nexorpanel/machine/fullchain.pem;
    ssl_certificate_key /etc/nexorpanel/machine/privkey.pem;

    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options DENY always;
    add_header Referrer-Policy same-origin always;

    # загрузка идёт кусками по 16 МиБ; умолчание nginx — один мегабайт, и с
    # ним не проходит ни один кусок
    client_max_body_size 64m;

    location / {
        proxy_pass http://$addr;
        proxy_set_header Host              \$host;
        proxy_set_header X-Real-IP         \$remote_addr;
        proxy_set_header X-Forwarded-For   \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
    }
}

# Заглушка на 80 порту.
#
# Это server по умолчанию: он отвечает на имена, которых нет среди сайтов, и
# на обращения по голому адресу машины. Без него там отвечал бы первый
# попавшийся сайт — и выглядело бы это так, будто он и есть эта машина.
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    location / {
        return 404 "404 Not Found\n";
        default_type text/plain;
    }
}
NGINX
	chmod 644 "$example"
	say "пример конфигурации nginx для панели: $example"
fi

# Вход в phpMyAdmin по ссылке: каталог ему называем, а его настройки не трогаем.
#
# Каталог ищется среди тех, куда его кладут пакеты, и только по собственному
# index.php: совпавшее по имени пустое место означало бы страницу агента,
# положенную не туда, где её кто-то прочитает.
#
# Настройки phpMyAdmin скрипт не правит, а кладёт рядом пример — по той же
# причине, по которой не правит конфигурацию nginx: это чужой файл, и чужое
# решение, отменённое молча, — худшее, что здесь можно сделать.
if blank "$AGENT" NEXOR_AGENT_PMA_SIGNON; then
	for dir in /usr/share/phpmyadmin /usr/share/phpMyAdmin /usr/share/nginx/html/phpmyadmin; do
		if [ -f "$ROOT$dir/index.php" ]; then
			put "$AGENT" NEXOR_AGENT_PMA_SIGNON "$dir"
			put "$AGENT" NEXOR_AGENT_PMA_SERVER 2
			say "вход в phpMyAdmin по ссылке: страница ляжет в $dir"
			break
		fi
	done
fi

signon="$ETC/phpmyadmin-signon.php.example"
if [ -n "$(value "$AGENT" NEXOR_AGENT_PMA_SIGNON)" ] && [ ! -f "$signon" ]; then
	cat > "$signon" <<'PMA'
<?php
// Вторая запись сервера — для входа по ссылке из панели.
//
// Первую оставляем как была: человек, пришедший в phpMyAdmin сам, по-прежнему
// входит своим паролем. auth_type стоит у записи, и заменив его у первой, вы
// оставили бы phpMyAdmin без формы входа вообще.
//
// Положите это в /etc/phpmyadmin/conf.d/nexor-signon.php (Debian/Ubuntu) или
// допишите в config.inc.php.
//
// SignonSession обязан совпадать с именем, которое пишет агент, — NexorSignon.
// Имя у этой сессии своё нарочно: сессию с собственным именем phpMyAdmin ведёт
// сам, и писать в неё значило бы мешать ему.

$i = 2;
$cfg['Servers'][$i]['host'] = 'localhost';
$cfg['Servers'][$i]['auth_type'] = 'signon';
$cfg['Servers'][$i]['SignonSession'] = 'NexorSignon';
$cfg['Servers'][$i]['SignonURL'] = 'nexor-signon.php';
$cfg['Servers'][$i]['verbose'] = 'NexorPanel';
PMA
	chmod 644 "$signon"
	say "настройки phpMyAdmin для входа по ссылке: $signon"
fi

if [ -z "$ROOT" ] && [ -d /run/systemd/system ]; then
	systemctl enable --now nexor-agent >/dev/null 2>&1 || \
		say "не удалось запустить nexor-agent — посмотрите systemctl status nexor-agent"
	# уже работающий агент читает настройки только при запуске
	if [ "$changed" = 1 ]; then
		systemctl try-restart nexor-agent >/dev/null 2>&1 || true
	fi
fi

exit 0
