From 4a87b682a0e66fa38255dd38d1bcc3a22c5bee7b Mon Sep 17 00:00:00 2001 From: vvzvlad Date: Sun, 16 Apr 2023 13:34:19 +0700 Subject: [PATCH] init commit --- README.md | 32 +++++++ apply_macs.service | 7 ++ apply_macs.sh | 10 +++ pushgit.service | 9 ++ pushgit.sh | 5 ++ pushgit.timer | 10 +++ requirements.txt | 13 +++ vestasync.py | 212 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 298 insertions(+) create mode 100644 README.md create mode 100644 apply_macs.service create mode 100644 apply_macs.sh create mode 100644 pushgit.service create mode 100644 pushgit.sh create mode 100644 pushgit.timer create mode 100644 requirements.txt create mode 100755 vestasync.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..4230bdb --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ + + + + + + + +Система запускает две службы: + +## Восстановление MAC-адресов (apply_macs) +Служба apply_macs отвечает за применение MAC-адресов к сетевым интерфейсам при загрузке системы. +Эта служба считывает MAC-адреса из файлов, расположенных в каталоге /mnt/data/etc/vestasync/macs/, если они есть, и присваивает их соответствующим интерфейсам, таким как eth0, eth1, wlan0 и т. д. Это используется, если на контроллер был восстанновлен созданный бекап, чтобы сохранять MAC-адреса старого контроллера, и соотвественно, адрес, выданный DHCP. +Для изменения MAC-адресов на изначальные надо просто удалить все файлы и перезагрузиться: +``` +rm -rf /mnt/data/etc/vestasync/macs/* +reboot +``` +Или, если надо сделать это временно, остановить службу: +```systemctl stop apply_macs.service``` +Обратно запустить: ``` systemctl start apply_macs.service``` +Узнать статус: ```systemctl status apply_macs.service``` + +## Автоматическое версионирование и деплой конфигов (pushgit) + +Служба pushgit, работает в паре с таймером pushgit.timer. +Они обеспечивают автоматическое сохранение конфигов в репозиторий Git на удаленном сервере ежедневно. +Это позволяет сохранять изменения в файлах и версионировать их, что упрощает управление конфигурационными файлами и предотвращает потерю данных при их случайном изменении или удалении. +Чтобы отключить сохранение, надо остановить службу: ```systemctl stop pushgit.timer``` +Запуск и проверка статуса аналогично предыдущек: + +Запустить: ``` systemctl start pushgit.timer``` +Узнать статус: ```systemctl status pushgit.timer``` diff --git a/apply_macs.service b/apply_macs.service new file mode 100644 index 0000000..e9fb388 --- /dev/null +++ b/apply_macs.service @@ -0,0 +1,7 @@ +[Unit] +Description=Apply MAC addresses to network interfaces +[Service] +Type=oneshot +ExecStart=/usr/local/bin/apply_macs.sh +[Install] +WantedBy=multi-user.target diff --git a/apply_macs.sh b/apply_macs.sh new file mode 100644 index 0000000..d1c7b5c --- /dev/null +++ b/apply_macs.sh @@ -0,0 +1,10 @@ +#!/bin/bash +macs_dir="/mnt/data/etc/vestasync/macs" + +for mac_file in "$macs_dir"/*; do + ifname=$(basename "$mac_file") + if [ -f "$mac_file" ]; then + mac_address=$(cat "$mac_file") + ip link set "$ifname" address "$mac_address" + fi +done diff --git a/pushgit.service b/pushgit.service new file mode 100644 index 0000000..5ee85f9 --- /dev/null +++ b/pushgit.service @@ -0,0 +1,9 @@ +[Unit] +Description=Push git changes daily + +[Service] +Type=oneshot +ExecStart=/mnt/data/etc/pushgit.sh + +[Install] +WantedBy=multi-user.target diff --git a/pushgit.sh b/pushgit.sh new file mode 100644 index 0000000..6d13263 --- /dev/null +++ b/pushgit.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +cd /mnt/data/etc/ > /dev/null 2>&1 || true +git add . > /dev/null 2>&1 || true +git commit -m "$(date)" > /dev/null 2>&1 || true +git push -u origin master > /dev/null 2>&1 || true diff --git a/pushgit.timer b/pushgit.timer new file mode 100644 index 0000000..97f8758 --- /dev/null +++ b/pushgit.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Run pushgit.service daily + +[Timer] +OnCalendar=daily +Persistent=true +Unit=pushgit.service + +[Install] +WantedBy=timers.target \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cb3d1b3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +bcrypt==4.0.1 +certifi==2022.12.7 +cffi==1.15.1 +charset-normalizer==3.1.0 +cryptography==40.0.2 +fabric==3.0.0 +idna==3.4 +invoke==2.0.0 +paramiko==3.1.0 +pycparser==2.21 +PyNaCl==1.5.0 +requests==2.28.2 +urllib3==1.26.15 diff --git a/vestasync.py b/vestasync.py new file mode 100755 index 0000000..f29a573 --- /dev/null +++ b/vestasync.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 + +from fabric import task, Connection +from invoke.exceptions import UnexpectedExit +from io import StringIO +import requests +import argparse +import re +import sys +import datetime +import json + + +device_user = "root" +gitea_user = "vestasync" +device_short_sn = "" + +cmd_parser = argparse.ArgumentParser(description='Process command line arguments.') +cmd_parser.add_argument('--cmd', help='Command (prepare, update, restore)', required=True) +cmd_args = cmd_parser.parse_known_args() + +cmd_args = cmd_args[0] + +main_parser = argparse.ArgumentParser(description='Process command line arguments.') +if cmd_args.cmd == "restore": + main_parser.add_argument('--source_hostname', help='Source device hostname', required=True) +else: + main_parser.add_argument('--device_new_name', help='Device new name', required=True) +main_parser.add_argument('--gitea_address', help='Gitea address string', required=True) +main_parser.add_argument('--gitea_token', help='Gitea token', required=True) +main_parser.add_argument('--device_ip', help='Device IP', required=True) + + +args = main_parser.parse_known_args() +args = args[0] + + + +def parse_address(address): + pattern = r'^(?Phttp|https)://(?P[^:]+):(?P\d+)(/.*|)$' + match = re.match(pattern, address) + + if match: + return match.group('protocol'), match.group('host'), match.group('port') + else: + raise ValueError("Invalid address format") + + + +def get_short_sn(c): + global device_short_sn + device_short_sn = c.run('wb-gen-serial -s', hide=True).stdout.strip() + if device_short_sn is None: + raise ValueError("Both device_new_name and device_short_sn must be provided") + +def set_hostname(c): + c.run(f'hostnamectl set-hostname {args.device_new_name}-{device_short_sn}') + +def restore_hostname(c): + c.run(f'hostnamectl set-hostname `cat /mnt/data/etc/vestasync/hostname`') + + +def prepare_packages_wb(c): + c.run('apt update') + c.run('apt install git apt-transport-https ca-certificates htop sudo mc wget curl jq zip gzip tar -y') + c.run('apt-get -y autoremove') + c.run('apt-get -y clean') + c.run('apt-get -y autoclean ') + + +def configure_git(c): + hostname = c.run('hostname', hide=True).stdout.strip() + c.run(f'git config --global user.name vestasync_wb_{hostname}') + c.run(f'git config --global user.email "vestasync@fake.mail"') + c.run(f'git config --global init.defaultBranch "master"') + +def create_repo(c): + hostname = c.run('hostname', hide=True).stdout.strip() + headers = {'Authorization': f'token {args.gitea_token}', 'Content-Type': 'application/json'} + data = {"name": hostname, "private": False} + response = requests.post(f'{args.vestasync_gitea_protocol}://{args.vestasync_gitea_host}:{args.vestasync_gitea_port}/api/v1/user/repos', headers=headers, json=data) + if response.status_code == 201: # 201 - Created, ожидаемый код успешного создания репозитория + print("Repository created successfully.") + elif response.status_code == 409: # 409 - Conflict, репозиторий уже существует + print("Error: Repository already exists.") + print("Exiting...") + sys.exit(1) + else: + print(f"Error: Unexpected HTTP status code {response.status_code}") + print("Exiting...") + sys.exit(1) + + +def init_repo(c): + hostname = c.run('hostname', hide=True).stdout.strip() + #c.run('cd /mnt/data/etc/ && git init') + #c.run(f'cd /mnt/data/etc/ && git remote add origin {args.vestasync_gitea_protocol}://{gitea_user}:{args.gitea_token}@{args.vestasync_gitea_host}:{args.vestasync_gitea_port}/{gitea_user}/{hostname}.git') + + +def create_autogit_sh(c): + c.run('echo "#!/usr/bin/env sh" > /mnt/data/etc/pushgit.sh') + c.run('echo "cd /mnt/data/etc/" >> /mnt/data/etc/pushgit.sh') + c.run('echo "git add ." >> /mnt/data/etc/pushgit.sh') + c.run('''echo 'git commit -m "$(date)"' >> /mnt/data/etc/pushgit.sh''') + c.run('echo "git push -u origin master" >> /mnt/data/etc/pushgit.sh') + c.run('chmod +x /mnt/data/etc/pushgit.sh') + c.run('(crontab -l; echo "0 0 * * * /mnt/data/etc/pushgit.sh > /dev/null 2>&1 ") | crontab -') + +def git_clone(c): + c.run(f'mkdir /mnt/data/{args.source_hostname}_etc ', hide=True) + c.run(f'git clone {args.vestasync_gitea_protocol}://{gitea_user}:{args.gitea_token}@{args.vestasync_gitea_host}:{args.vestasync_gitea_port}/{gitea_user}/{args.source_hostname}.git /mnt/data/{args.source_hostname}_etc') + +def copy_etc(c): + current_date = datetime.datetime.now().strftime("%Y-%m-%d") + archive_name = f"backup_{current_date}.tar.gz" + print(f"Remove old .git...") + c.run(f"rm -rf /mnt/data/etc/.git") + print(f"Create backup: /mnt/data/{archive_name}") + c.run(f"tar -czvf /mnt/data/{archive_name} -C /mnt/data etc", hide=True) + + files_and_folders = c.run("find /mnt/data/WB2-A3TBJXLS_etc", hide=True).stdout.strip().split('\n') + files_and_folders = [item for item in files_and_folders if ".git" not in item] + + for item in files_and_folders: + dest_item = item.replace(f"/{args.source_hostname}_etc/", "/etc/") + if c.run(f"test -f {item}", hide=True, warn=True).ok: + c.run(f"cat {item} > {dest_item}") + elif c.run(f"test -d {item}", hide=True, warn=True).ok: + c.run(f"mkdir -p {dest_item}") + print(f"Restore: {item} -> {dest_item}") + + print(f"Сopy source .git...") + c.run(f"cp -R /mnt/data/{args.source_hostname}_etc/.git /mnt/data/etc/.git") + + print(f"Remove source etc...") + c.run(f"rm -rf /mnt/data/{args.source_hostname}_etc") + + print(f"Restore completed") + +def ppush_the_repo(c): + c.run('cd /mnt/data/etc/ && git add .', hide=True) + try: + c.run('cd /mnt/data/etc/ && git commit -m "$(date)"', hide=True) + except UnexpectedExit as e: + if 'nothing to commit' in e.result.stdout: + print("Nothing to commit, exit") + else: + print(f"Error: {e.result.stderr}") + c.run('cd /mnt/data/etc/ && git push -u origin master', hide=True) + + +def save_mac_in_cfg(c): + hostname = c.run('hostname', hide=True).stdout.strip() + interfaces_info = c.run("ip -j a", hide=True).stdout.strip() + interfaces_data = json.loads(interfaces_info) + c.run("mkdir -p /mnt/data/etc/vestasync/macs") + for interface in interfaces_data: + ifname = interface["ifname"] + if re.match(r'^(eth|wlan)', ifname): + mac_address = interface["address"] + c.run(f"echo {mac_address} > /mnt/data/etc/vestasync/macs/{ifname}") + c.run(f'echo {hostname} > /mnt/data/etc/vestasync/hostname') + + +def device_prepare(): + with Connection(host=args.device_ip, user=device_user) as c: + #prepare_packages_wb(c) + #configure_git(c) + #get_short_sn(c) + #set_hostname(c) + #create_repo(c) + #init_repo(c) + #ppush_the_repo(c) + save_mac_in_cfg(c) + ppush_the_repo(c) + +def device_update(): + with Connection(host=args.device_ip, user=device_user) as c: + ppush_the_repo(c) + +def device_restore(): + with Connection(host=args.device_ip, user=device_user) as c: + #prepare_packages_wb(c) + #configure_git(c) + git_clone(c) + copy_etc(c) + ppush_the_repo(c) + restore_hostname(c) + create_autogit_sh(c) + + +if __name__ == '__main__': + try: + args.vestasync_gitea_protocol, args.vestasync_gitea_host, args.vestasync_gitea_port = parse_address(args.gitea_address) + del args.gitea_address + except ValueError as e: + print(e) + exit(1) + + if cmd_args.cmd == "prepare": + device_prepare() + if cmd_args.cmd == "update": + device_update() + if cmd_args.cmd == "restore": + device_restore() + + + + + + +