From fa31227cff96a170be76508da63f3c64bf635212 Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Thu, 5 Sep 2024 18:22:38 +0300 Subject: [PATCH] feat: Shutdown on lan --- nixos/modules/bundle.nix | 1 + nixos/modules/shutdown-on-lan.nix | 70 +++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 nixos/modules/shutdown-on-lan.nix diff --git a/nixos/modules/bundle.nix b/nixos/modules/bundle.nix index fd2d104..23efd00 100644 --- a/nixos/modules/bundle.nix +++ b/nixos/modules/bundle.nix @@ -13,5 +13,6 @@ ./vpn.nix ./printing.nix ./adb.nix + ./shutdown-on-lan.nix ]; } diff --git a/nixos/modules/shutdown-on-lan.nix b/nixos/modules/shutdown-on-lan.nix new file mode 100644 index 0000000..421b4c5 --- /dev/null +++ b/nixos/modules/shutdown-on-lan.nix @@ -0,0 +1,70 @@ +{ pkgs, lib, ... }: + let + sol = pkgs.writers.writePython3 "shutdown-on-lan.py" { + libraries = [ pkgs.python312Packages.psutil ]; + flakeIgnore = [ "E501" "E302" "E305" ]; + } /*py*/ '' + # https://habr.com/ru/articles/816765/ + + import socket + import os + import logging + import psutil + from time import sleep + + WOL_PORT = 9 + + logging.basicConfig(format='%(levelname)s: %(asctime)s %(message)s', level=logging.INFO) + logger = logging.getLogger(__name__) + + def get_ip_mac_address() -> tuple: + ip_addr = mac_addr = None + + while not ip_addr or not mac_addr or ip_addr == '127.0.0.1': + net = psutil.net_if_addrs() + for item in net[list(net.keys())[-1]]: + addr = item.address + # В IPv4-адресах разделители - точки + if '.' in addr: + ip_addr = addr + # В MAC-адресах разделители либо тире, либо одинарное двоеточие. + # Двойное двоеточие - это разделители для адресов IPv6 + elif ('-' in addr or ':' in addr) and '::' not in addr: + # Приводим MAC-адрес к одному формату. Формат может меняться в зависимости от ОС + mac_addr = addr.replace(':', '-').upper() + if not ip_addr or not mac_addr or ip_addr == '127.0.0.1': + logger.debug('Не удалось получить IP или MAC-адрес сетевого интерфейса') + sleep(10) + return ip_addr, mac_addr + + def assemble_wol_packet(mac_address: str) -> str: + return f'{"FF-" * 6}{(mac_address + "-") * 16}' + + def check_is_wol_packet(raw_bytes: bytes, assembled_wol_packet: str) -> int: + return '-'.join(f'{byte:02x}' for byte in raw_bytes).upper() + '-' == assembled_wol_packet + + def run_udp_port_listener(port: int): + ip_addr, mac_addr = get_ip_mac_address() + server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + server_socket.bind((ip_addr, port)) + logger.info(f'Listening on {ip_addr}:{port}') + assembled_wol_packet = assemble_wol_packet(mac_addr) + while True: + data, _ = server_socket.recvfrom(1024) + if check_is_wol_packet(data, assembled_wol_packet): + if os.name == 'posix': + os.system('shutdown -h now') + elif os.name == 'nt': + os.system('shutdown -s -t 0 -f') + run_udp_port_listener(WOL_PORT) + ''; + in { + systemd.services.shutdown-on-lan = { + enable = true; + after = [ "network.target" ]; + wantedBy = [ "default.target" ]; + serviceConfig.ExecStart = sol; + }; + + networking.firewall.allowedUDPPorts = [ 9 ]; + }