osgos_postinstall/deb_dist/postinstall-1.0.1/postinstall/__init__.py

396 lines
14 KiB
Python
Raw Normal View History

2025-06-09 14:32:16 +05:00
import os
import subprocess
import sys
import signal
from dialog import Dialog
from postinstall.root import check_root
from postinstall.option import (
network,
update,
add_user,
install_packages,
ssh,
hostname,
shell,
database,
docker,
)
import json
check_root()
from postinstall.logger import logger
# import debugpy
# debugpy.listen(("0.0.0.0", 5678))
# debugpy.wait_for_client()
SSH_CONFIG_PATH = "/etc/ssh/sshd_config"
HOSTNAME_FILE = "/etc/hostname"
AUTOSTART_FILE = "/usr/share/postinstall/.norestart"
BACKUPS_FILE = "/usr/share/postinstall/backups.json"
UFW_OPENSSH_CONFIG_PATH = "/etc/ufw/applications.d/openssh-server"
logging = logger
def get_hostname() -> str:
try:
with open(HOSTNAME_FILE, "r") as f:
return f.read().strip()
except IOError as e:
logging.error(f"Ошибка при чтении hostname: {e}")
return "localhost"
def get_ssh_port() -> str:
try:
result = subprocess.run(
["grep", "^#*Port", SSH_CONFIG_PATH],
capture_output=True,
text=True,
check=True,
)
port = result.stdout.strip().split()[-1]
return port if port.isdigit() else "22"
except subprocess.CalledProcessError:
logging.warning("Не удалось получить порт SSH, используется порт по умолчанию")
return "22"
class MenuScreen:
def __init__(self):
self.d = Dialog(dialog="dialog")
self.ssh_port = get_ssh_port()
self.hostname = get_hostname()
self.ssh_changes = None
self.new_hostname = None
self.network = None
self.autostart_enabled = self.check_auto_start()
self.new_autostart_enabled = self.autostart_enabled
self.options = [
{
"name": "Сетевые настройки",
"value": "",
"tabs": "\t \t",
"option": "network",
},
{
"name": "Обновление системы",
"value": "",
"tabs": "\t \t",
"option": "update",
},
{
"name": "Добавить пользователя",
"value": "",
"tabs": "\t \t",
"option": "add_user",
},
{
"name": "Установка программ",
"value": "",
"tabs": "\t \t",
"option": "install_packages",
},
{
"name": "Настройки ssh",
"value": "",
"tabs": "\t \t",
"option": "ssh",
},
{
"name": "Изменить доменное имя ПК",
"value": self.hostname,
"tabs": "\t \t",
"option": "hostname",
},
{"name": "Выбор оболочки", "value": "", "tabs": "\t \t", "option": "shell"},
{
"name": "Автозапуск при следующем запуске",
"value": "Включен" if self.new_autostart_enabled else "Отключен",
"tabs": "\t",
"option": "auto_start",
},
{
"name": "Настройка базы данных",
"value": "",
"tabs": "\t \t",
"option": "database",
},
{
"name": "Настройка docker",
"value": "",
"tabs": "\t \t",
"option": "docker",
},
{
"name": "Откат последних изменений",
"value": "",
"tabs": "\t \t",
"option": "rollback",
},
{
"name": "Сохранить и выйти",
"value": "",
"tabs": "\t \t",
"option": "save_and_exit",
},
]
os.makedirs(os.path.dirname(AUTOSTART_FILE), exist_ok=True)
def show_menu(self):
choices = []
for i, option in enumerate(self.options, 1):
value = option["value"]
if option["option"] == "hostname":
value = (
self.new_hostname
if self.new_hostname is not None
else self.hostname
)
if self.new_hostname is not None:
value = f"{value} (не применено)"
elif option["option"] == "network":
value = "Изменено (не применено)" if self.network else ""
choice_text = f"{option['name']}{option['tabs']}{value}"
choices.append((str(i), choice_text))
code, tag = self.d.menu(
"Выберите опцию:",
choices=choices,
title="Главное меню",
cancel_label="Выйти без сохранения",
width=70,
height=15,
)
if code == self.d.OK and tag:
return self.options[int(tag) - 1]["option"]
return None
def set_temp_network(self, settings):
self.temp_network = settings
def check_auto_start(self) -> bool:
return not os.path.exists(AUTOSTART_FILE)
def set_auto_start(self, enable: bool) -> None:
try:
if enable:
if os.path.exists(AUTOSTART_FILE):
os.remove(AUTOSTART_FILE)
logging.info("Автозапуск включен")
else:
with open(AUTOSTART_FILE, "w") as f:
f.write("# Это файл создан для отключения автозапуска\n")
logging.info("Автозапуск отключен")
except Exception as e:
logging.error(f"Ошибка при изменении автозапуска: {e}")
def update_ssh_port(self, new_port):
self.new_ssh_port = new_port
def update_hostname(self, new_hostname):
self.new_hostname = new_hostname
def update_autostart(self, new_state):
self.new_autostart_enabled = new_state
def choose(option, menu):
if menu.new_autostart_enabled != menu.autostart_enabled:
menu.set_auto_start(menu.new_autostart_enabled)
if option == "network":
net_config = network.NetworkConfig()
if net_config.run():
menu.network = net_config
elif option == "update":
logging.info("Начало обновления системы.")
update.run_update()
logging.info("Завершено обновление системы.")
elif option == "add_user":
logging.info("Началось добавления пользователя.")
add_user.run_add_user()
logging.info("Завершено добавление пользователя.")
elif option == "install_packages":
logging.info("Начало установки пакетов.")
install_packages.run_install_packages()
logging.info("Завершена установка пакетов.")
elif option == "ssh":
ssh_config, changes_made = ssh.run_ssh_config()
if changes_made:
menu.ssh_changes = ssh_config
elif option == "hostname":
new_hostname = hostname.run_change_hostname()
if new_hostname:
menu.new_hostname = new_hostname
menu.options[5]["value"] = f"{new_hostname} (не применено)"
elif option == "shell":
logging.info("Началось изменение shell.")
shell.run_change_shell()
logging.info("Завершено изменение shell.")
elif option == "auto_start":
menu.new_autostart_enabled = not menu.new_autostart_enabled
menu.options[7]["value"] = (
"Включен" if menu.new_autostart_enabled else "Отключен"
)
logging.info(
f"Автозапуск {'включен' if menu.new_autostart_enabled else 'отключен'}"
)
elif option == "database":
logging.info("Запуск настройки базы данных.")
# Открытие диалогового окна для выбора базы данных
code, choice = menu.d.menu("Выберите базу данных для установки:",
choices=[("PostgreSQL", "Установить PostgreSQL"),
("MariaDB", "Установить MariaDB")])
if code == menu.d.OK:
if choice == "PostgreSQL":
if not database.is_installed('postgresql-16'):
database.install_postgresql(menu.d)
else:
menu.d.msgbox("PostgreSQL и Harlequin уже установлены.")
elif choice == "MariaDB":
if not database.is_installed('mariadb-server'):
database.install_mariadb(menu.d)
else:
menu.d.msgbox("MariaDB уже установлена.")
# Независимо от выбранной базы данных, предложить установку Webmin
if not database.is_installed('webmin'):
code = menu.d.yesno("Установить Webmin?", yes_label="Да", no_label="Нет")
if code == menu.d.OK:
database.install_webmin(menu.d)
else:
menu.d.msgbox("Webmin уже установлен.")
logging.info("Настройка базы данных завершена.")
else:
menu.d.msgbox("Выбор базы данных отменён")
elif option == "docker":
docker.run()
elif option == "rollback":
try:
with open(BACKUPS_FILE, "r") as f:
backups = json.load(f)
f.close()
for i in backups:
with open(i["file"], "w") as f:
f.write(i["content"])
f.close()
subprocess.run(["systemctl", "restart", "networking"], check=True)
subprocess.run(["sudo", "systemctl", "restart", "sshd"], check=True)
ssh_port = get_ssh_port()
subprocess.run(
[
"sudo",
"sed",
"-i",
f"s/^ports=.*/ports={ssh_port}\/tcp/",
UFW_OPENSSH_CONFIG_PATH,
],
check=True,
)
subprocess.run(["sudo", "ufw", "app", "update", "OpenSSH"], check=True)
subprocess.run(["sudo", "ufw", "delete", "allow", "OpenSSH"], check=True)
subprocess.run(["sudo", "ufw", "allow", "OpenSSH"], check=True)
menu.d.msgbox("Резервные копии успешно восстановлены")
logging.info("Резервные копии успешно восстановлены")
except Exception as e:
menu.d.msgbox(f"Вы еще не применяли изменения.{e}")
return False
elif option == "save_and_exit":
with open("/etc/network/interfaces", "r") as f:
interfaces = f.read()
f.close()
with open("/etc/resolv.conf", "r") as f:
dns_settings = f.read()
f.close()
with open("/etc/ssh/sshd_config", "r") as f:
ssh_settings = f.read()
f.close()
try:
with open("/etc/ssh/sshd_config.d/auth.conf", "r") as f:
ssh_auth = f.read()
except Exception:
ssh_auth = ""
backup = [
{
"file": "/etc/network/interfaces",
"content": interfaces,
},
{
"file": "/etc/resolv.conf",
"content": dns_settings,
},
{
"file": "/etc/ssh/sshd_config",
"content": ssh_settings,
},
{
"file": "/etc/ssh/sshd_config.d/auth.conf",
"content": ssh_auth,
},
]
with open(BACKUPS_FILE, "w") as f:
f.write(json.dumps(backup))
f.close()
if menu.ssh_changes:
logging.info(
f"Применены новые настройки SSH. {'Новый порт SSH: ' + str(menu.ssh_changes.new_port) if menu.ssh_changes.new_port is not None else ''} {'Новый метод авторизации: ' + menu.ssh_changes.new_auth_method if menu.ssh_changes.new_auth_method is not None else ''}"
)
menu.ssh_changes.apply_changes()
if menu.new_hostname:
logging.info(f"Применено новое имя хоста: {menu.new_hostname}")
if hostname.apply_hostname_change(menu.new_hostname):
menu.hostname = menu.new_hostname
if menu.network:
logging.info(
f"Применены новые настройки сети: {menu.network.temp_network_settings}"
)
if not menu.network.save_and_apply_network_settings():
return True
return False
return True
def signal_handler(sig, frame):
logging.info("Приложение закрыто")
subprocess.run("clear", shell=True)
sys.exit(0)
def main():
logging.info("Приложение запущено")
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
menu = MenuScreen()
while True:
option = menu.show_menu()
if option is None:
break
if not choose(option, menu):
break
logging.info("Приложение закрыто")
subprocess.run("clear", shell=True)
if __name__ == "__main__":
main()