stabilty and multiple interfaces
This commit is contained in:
@@ -1,20 +1,69 @@
|
|||||||
# pyLCDInfo
|
# pyLCDInfo
|
||||||
|
|
||||||
display current linux/network info on a 16x2 LCD via I2C
|
Display current Linux/network info on a 16×2 LCD via I2C.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Shows IPv4 and IPv6 for **multiple** network interfaces
|
||||||
|
- Displays `<interface> not connected` gracefully when an interface is unavailable — no crashes
|
||||||
|
- WireGuard VPN state detection with animated waiting screen
|
||||||
|
- Startup sequence showing kernel version, OS version, MAC addresses, and WireGuard endpoint
|
||||||
|
- Backlight turns off automatically once VPN is connected
|
||||||
|
|
||||||
|
## Startup screens (2 s each)
|
||||||
|
|
||||||
|
| Screen | Content |
|
||||||
|
|---|---|
|
||||||
|
| Welcome | "Offsite Backup" banner |
|
||||||
|
| Kernel | Running kernel version (`uname -r`) |
|
||||||
|
| OS | Pretty name from `/etc/os-release` |
|
||||||
|
| MAC \<iface\> | MAC address per interface (repeated for each) |
|
||||||
|
| WG Endpoint | Resolved hostname or IP of the WireGuard peer |
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
`apt-get install i2c-tools pip python3-dev`
|
```bash
|
||||||
|
apt-get install i2c-tools pip python3-dev
|
||||||
|
pip3 install rpi_lcd netifaces
|
||||||
|
```
|
||||||
|
|
||||||
`pip3 install rpi_lcd`
|
Search for the I2C LCD controller on bus X (0, 1, 2, 3, …) and note the device address:
|
||||||
|
|
||||||
search I2C LCD Controller on Bus X (0, 1, 2, 3, ...) and note address of device
|
```bash
|
||||||
`i2cdetect -y X`
|
i2cdetect -y X
|
||||||
|
```
|
||||||
|
|
||||||
`nano /lib/systemd/system/LCDinfo.service`
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
|
python3 pyLCDinfo.py <iface1> [iface2 ...] <wireguard_iface> <disk_path>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arguments (in order):**
|
||||||
|
|
||||||
|
| Position | Description |
|
||||||
|
|---|---|
|
||||||
|
| `iface1 [iface2 ...]` | One or more local network interfaces (e.g. `eth0 eth1`) |
|
||||||
|
| `wireguard_iface` | WireGuard interface name (e.g. `wg0`) |
|
||||||
|
| `disk_path` | Path to monitor for free disk space (e.g. `/mnt/hdd/backups`) |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Single interface
|
||||||
|
python3 pyLCDinfo.py eth0 wg0 /mnt/hdd/backups
|
||||||
|
|
||||||
|
# Multiple interfaces
|
||||||
|
python3 pyLCDinfo.py eth0 eth1 wlan0 wg0 /mnt/hdd/backups
|
||||||
|
```
|
||||||
|
|
||||||
|
## systemd service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano /lib/systemd/system/LCDinfo.service
|
||||||
|
```
|
||||||
|
|
||||||
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=LCD info
|
Description=LCD info
|
||||||
After=syslog.target
|
After=syslog.target
|
||||||
@@ -26,12 +75,14 @@ User=root
|
|||||||
Group=root
|
Group=root
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5s
|
RestartSec=5s
|
||||||
ExecStart=sudo /usr/bin/nice -n 19 sudo -u root /usr/bin/python3 /root/pyLCDinfo.py eth0 wg0 /mnt/hdd/backups
|
ExecStart=sudo /usr/bin/nice -n 19 sudo -u root /usr/bin/python3 /root/pyLCDinfo.py end0 wlx000f007740cd wg0 /mnt/hdd/backups
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
`systemctl daemon-reload`
|
```bash
|
||||||
|
systemctl daemon-reload
|
||||||
`systemctl enable /lib/systemd/system/LCDinfo.service`
|
systemctl enable /lib/systemd/system/LCDinfo.service
|
||||||
|
systemctl start LCDinfo.service
|
||||||
|
```
|
||||||
|
|||||||
+262
-76
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
""" Author: Hendrik Schutter, mail@hendrikschutter.com
|
"""Author: Hendrik Schutter, mail@hendrikschutter.com
|
||||||
Date of creation: 2022/05/23
|
Date of creation: 2022/05/23
|
||||||
Date of last modification: 2022/10/17
|
Date of last modification: 2026/08/21
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -12,123 +12,309 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import socket
|
||||||
|
|
||||||
lcd = LCD(address=0x3f, bus=2, width=16, rows=2, backlight=True)
|
lcd = LCD(address=0x3F, bus=2, width=16, rows=2, backlight=True)
|
||||||
|
|
||||||
def get_ip_addr(interfce_name):
|
LCD_WIDTH = 16
|
||||||
return {
|
|
||||||
"ipv4": ni.ifaddresses(interfce_name)[ni.AF_INET][0]['addr'],
|
|
||||||
"ipv6": ni.ifaddresses(interfce_name)[ni.AF_INET6][0]['addr'] ,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_wireguard_state(config_name):
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Helper: truncate a string to fit the LCD width
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def lcd_fit(text: str, width: int = LCD_WIDTH) -> str:
|
||||||
|
"""Truncate text to at most `width` characters."""
|
||||||
|
return text[:width]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Network helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def get_ip_addr(interface_name: str) -> dict:
|
||||||
|
"""Return IPv4 and IPv6 address for the given interface.
|
||||||
|
|
||||||
|
Returns a dict with keys 'ipv4' and 'ipv6'. Values are the address string
|
||||||
|
or None when not available.
|
||||||
|
"""
|
||||||
|
result = {"ipv4": None, "ipv6": None}
|
||||||
try:
|
try:
|
||||||
last_handshake_unix_timestamp = subprocess.check_output(f"wg show {config_name} latest-handshakes",
|
addrs = ni.ifaddresses(interface_name)
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
if ni.AF_INET in addrs:
|
||||||
|
result["ipv4"] = addrs[ni.AF_INET][0].get("addr")
|
||||||
|
if ni.AF_INET6 in addrs:
|
||||||
|
result["ipv6"] = addrs[ni.AF_INET6][0].get("addr")
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
pass
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_mac_addr(interface_name: str) -> str | None:
|
||||||
|
"""Return the MAC address for the given interface, or None on error."""
|
||||||
|
try:
|
||||||
|
addrs = ni.ifaddresses(interface_name)
|
||||||
|
if ni.AF_LINK in addrs:
|
||||||
|
return addrs[ni.AF_LINK][0].get("addr")
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_interface_up(interface_name: str) -> bool:
|
||||||
|
"""Return True when the interface exists and has an IPv4 address."""
|
||||||
|
try:
|
||||||
|
addrs = ni.ifaddresses(interface_name)
|
||||||
|
return ni.AF_INET in addrs
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# WireGuard helper
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def get_wireguard_state(config_name: str) -> bool:
|
||||||
|
"""Return True when the WireGuard interface reports a latest handshake."""
|
||||||
|
try:
|
||||||
|
subprocess.check_output(
|
||||||
|
f"wg show {config_name} latest-handshakes",
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
return True
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return False
|
return False
|
||||||
return True
|
|
||||||
|
|
||||||
def get_disk_usage(disk_path):
|
|
||||||
total, used, free = shutil.disk_usage(disk_path)
|
|
||||||
return str(format((free // (2**30)) / (total // (2**30)) * 100.0,'.2f')) + '%'
|
|
||||||
|
|
||||||
def get_uptime():
|
|
||||||
with open('/proc/uptime', 'r') as f:
|
|
||||||
uptime_seconds = float(f.readline().split()[0])
|
|
||||||
if (uptime_seconds >= (60.0*60.0*24.0)):
|
|
||||||
return str(format(uptime_seconds/(60.0*60.0*24.0),'.2f')) + ' days'
|
|
||||||
else:
|
|
||||||
return str(format((uptime_seconds/(60.0*60.0)),'.2f')) + ' hours'
|
|
||||||
|
|
||||||
|
|
||||||
return strftime("%H:%M:%S", gmtime(60*60*24))
|
def get_wireguard_endpoint(config_name: str) -> str | None:
|
||||||
|
"""Return the endpoint hostname/IP of the first WireGuard peer, or None."""
|
||||||
|
try:
|
||||||
|
output = (
|
||||||
|
subprocess.check_output(
|
||||||
|
f"wg show {config_name} endpoints",
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
.decode()
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
|
# Output format: "<pubkey>\t<endpoint_ip>:<port>"
|
||||||
|
if output:
|
||||||
|
parts = output.split()
|
||||||
|
if len(parts) >= 2:
|
||||||
|
endpoint = parts[1] # "<ip>:<port>" or "[ipv6]:port"
|
||||||
|
# Try to resolve hostname; fall back to raw IP
|
||||||
|
host = endpoint.rsplit(":", 1)[0].strip("[]")
|
||||||
|
try:
|
||||||
|
hostname = socket.gethostbyaddr(host)[0]
|
||||||
|
return hostname
|
||||||
|
except socket.herror:
|
||||||
|
return host
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_system_time():
|
# ---------------------------------------------------------------------------
|
||||||
|
# System info helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def get_kernel_version() -> str:
|
||||||
|
"""Return the running kernel version string."""
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
subprocess.check_output("uname -r", shell=True, stderr=subprocess.STDOUT)
|
||||||
|
.decode()
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def get_os_version() -> str:
|
||||||
|
"""Return a short OS description from /etc/os-release."""
|
||||||
|
try:
|
||||||
|
with open("/etc/os-release", "r") as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith("PRETTY_NAME="):
|
||||||
|
return line.split("=", 1)[1].strip().strip('"')
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return "unknown OS"
|
||||||
|
|
||||||
|
|
||||||
|
def get_disk_usage(disk_path: str) -> str:
|
||||||
|
"""Return free disk space as a percentage string, or an error string."""
|
||||||
|
try:
|
||||||
|
total, used, free = shutil.disk_usage(disk_path)
|
||||||
|
if total == 0:
|
||||||
|
return "N/A"
|
||||||
|
pct = (free // (2**30)) / (total // (2**30)) * 100.0
|
||||||
|
return f"{pct:.2f}%"
|
||||||
|
except OSError:
|
||||||
|
return "disk error"
|
||||||
|
|
||||||
|
|
||||||
|
def get_uptime() -> str:
|
||||||
|
"""Return system uptime as a human-readable string."""
|
||||||
|
try:
|
||||||
|
with open("/proc/uptime", "r") as f:
|
||||||
|
uptime_seconds = float(f.readline().split()[0])
|
||||||
|
if uptime_seconds >= 60.0 * 60.0 * 24.0:
|
||||||
|
return f"{uptime_seconds / (60.0 * 60.0 * 24.0):.2f} days"
|
||||||
|
return f"{uptime_seconds / 3600.0:.2f} hours"
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_time() -> str:
|
||||||
|
"""Return the current local time as HH:MM."""
|
||||||
return datetime.now().strftime("%H:%M")
|
return datetime.now().strftime("%H:%M")
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
if len(sys.argv) != 4:
|
# ---------------------------------------------------------------------------
|
||||||
print("exiting due to few arguments")
|
# LCD helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def show_two_line(
|
||||||
|
top: str, bottom: str, duration: float, align_bottom: str = "right"
|
||||||
|
) -> None:
|
||||||
|
"""Display two lines on the LCD and wait for `duration` seconds."""
|
||||||
|
lcd.clear()
|
||||||
|
lcd.text(lcd_fit(top), 1)
|
||||||
|
lcd.text(lcd_fit(bottom), 2, align=align_bottom)
|
||||||
|
time.sleep(duration)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Startup sequence
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def show_startup_screens(interfaces: list[str], wireguard_interface: str) -> None:
|
||||||
|
"""Display system info screens at startup (2 s each)."""
|
||||||
|
|
||||||
|
# Welcome banner
|
||||||
|
show_two_line(" Offsite Backup", " fckaf.de/FHA", 2.0, align_bottom="left")
|
||||||
|
|
||||||
|
# Kernel version
|
||||||
|
kernel = get_kernel_version()
|
||||||
|
show_two_line("Kernel:", kernel, 2.0)
|
||||||
|
|
||||||
|
# OS version
|
||||||
|
os_ver = get_os_version()
|
||||||
|
show_two_line("OS:", os_ver, 2.0)
|
||||||
|
|
||||||
|
# MAC address for each interface
|
||||||
|
for iface in interfaces:
|
||||||
|
mac = get_mac_addr(iface)
|
||||||
|
if mac:
|
||||||
|
show_two_line(f"MAC {iface}:", mac, 2.0)
|
||||||
|
else:
|
||||||
|
show_two_line(f"MAC {iface}:", "not available", 2.0)
|
||||||
|
|
||||||
|
# WireGuard endpoint
|
||||||
|
endpoint = get_wireguard_endpoint(wireguard_interface)
|
||||||
|
if endpoint:
|
||||||
|
show_two_line("WG Endpoint:", endpoint, 2.0)
|
||||||
|
else:
|
||||||
|
show_two_line("WG Endpoint:", "not available", 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Interface display helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def show_interface_info(iface: str, ip_version: str, duration: float) -> None:
|
||||||
|
"""Show IPv4 or IPv6 address for an interface, or a 'not connected' message."""
|
||||||
|
label = f"{ip_version} {iface}:"
|
||||||
|
addrs = get_ip_addr(iface)
|
||||||
|
|
||||||
|
if ip_version == "IPv4":
|
||||||
|
addr = addrs["ipv4"]
|
||||||
|
else:
|
||||||
|
raw = addrs["ipv6"]
|
||||||
|
# Strip well-known link-local prefix suffix used in display
|
||||||
|
addr = raw[14:-1] if raw and len(raw) > 15 else raw
|
||||||
|
|
||||||
|
if addr:
|
||||||
|
show_two_line(label, addr, duration)
|
||||||
|
else:
|
||||||
|
show_two_line(label, f"{iface} not connected", duration, align_bottom="left")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Main loop
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
def main() -> None:
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print("Usage: pyLCDinfo.py <iface1> [iface2 ...] <wireguard_iface> <disk_path>")
|
||||||
|
print(
|
||||||
|
" At least one local interface, one WireGuard interface, and a disk path are required."
|
||||||
|
)
|
||||||
exit(-1)
|
exit(-1)
|
||||||
|
|
||||||
local_interface = sys.argv[1]
|
# Last argument: disk path
|
||||||
wireguard_interface = sys.argv[2]
|
disk_path = sys.argv[-1]
|
||||||
disk_path = sys.argv[3]
|
# Second-to-last argument: WireGuard interface
|
||||||
|
wireguard_interface = sys.argv[-2]
|
||||||
|
# Everything before that: local interfaces (at least one)
|
||||||
|
local_interfaces = sys.argv[1:-2]
|
||||||
|
|
||||||
#print(get_ip_addr(local_interface))
|
if not local_interfaces:
|
||||||
#print(get_wireguard_state(wireguard_interface))
|
print("Error: at least one local interface must be provided.")
|
||||||
#print(get_disk_usage(disk_path))
|
exit(-1)
|
||||||
#print(get_uptime())
|
|
||||||
#print(get_system_time())
|
|
||||||
|
|
||||||
lcd.clear()
|
# --- Startup screens ---
|
||||||
lcd.text(" Offsite Backup", 1)
|
show_startup_screens(local_interfaces, wireguard_interface)
|
||||||
lcd.text(" fckaf.de/FHA", 2)
|
|
||||||
time.sleep(2.0)
|
|
||||||
|
|
||||||
state_ok = False
|
state_ok = False
|
||||||
|
|
||||||
while(True):
|
while True:
|
||||||
if state_ok == False:
|
if not state_ok:
|
||||||
lcd.backlight(turn_on=True)
|
lcd.backlight(turn_on=True)
|
||||||
|
|
||||||
lcd.clear()
|
# Show IPv4 and IPv6 for each local interface
|
||||||
lcd.text("Local IPv4:", 1)
|
for iface in local_interfaces:
|
||||||
lcd.text(get_ip_addr(local_interface)["ipv4"], 2, align='right')
|
show_interface_info(iface, "IPv4", 2.4)
|
||||||
time.sleep(2.4)
|
show_interface_info(iface, "IPv6", 2.4)
|
||||||
lcd.clear()
|
|
||||||
lcd.text("Local IPv6:", 1)
|
|
||||||
lcd.text(get_ip_addr(local_interface)["ipv6"][14:-1], 2, align='right')
|
|
||||||
time.sleep(2.4)
|
|
||||||
|
|
||||||
|
# WireGuard waiting animation
|
||||||
lcd.clear()
|
lcd.clear()
|
||||||
lcd.text("Waiting for VPN", 1)
|
lcd.text("Waiting for VPN", 1)
|
||||||
|
|
||||||
for loops in range(4):
|
for loops in range(4):
|
||||||
for fill in range(14):
|
for fill in range(14):
|
||||||
if loops % 2 == 0:
|
if loops % 2 == 0:
|
||||||
lcd.text("<>".rjust(fill), 2, align='left')
|
lcd.text("<>".rjust(fill), 2, align="left")
|
||||||
else:
|
else:
|
||||||
lcd.text("<>".ljust(fill), 2, align='right')
|
lcd.text("<>".ljust(fill), 2, align="right")
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
|
||||||
if get_wireguard_state(wireguard_interface):
|
if get_wireguard_state(wireguard_interface):
|
||||||
state_ok = True
|
state_ok = True
|
||||||
lcd.clear()
|
lcd.clear()
|
||||||
lcd.text(" VPN connected ", 1)
|
lcd.text(" VPN connected ", 1)
|
||||||
lcd.text(get_system_time().rjust(16-len(get_system_time())), 2, align='left')
|
t = get_system_time()
|
||||||
|
lcd.text(t.rjust(LCD_WIDTH - len(t)), 2, align="left")
|
||||||
time.sleep(10.0)
|
time.sleep(10.0)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
lcd.backlight(turn_on=False)
|
lcd.backlight(turn_on=False)
|
||||||
|
|
||||||
lcd.clear()
|
# Show IPv4 and IPv6 for each local interface
|
||||||
lcd.text("Local IPv4:", 1)
|
for iface in local_interfaces:
|
||||||
lcd.text(get_ip_addr(local_interface)["ipv4"], 2, align='right')
|
show_interface_info(iface, "IPv4", 5.0)
|
||||||
time.sleep(5.0)
|
show_interface_info(iface, "IPv6", 5.0)
|
||||||
lcd.clear()
|
|
||||||
lcd.text("Local IPv6:", 1)
|
|
||||||
lcd.text(get_ip_addr(local_interface)["ipv6"][14:-1], 2, align='right')
|
|
||||||
time.sleep(5.0)
|
|
||||||
lcd.clear()
|
|
||||||
lcd.text("Free Disk:", 1)
|
|
||||||
lcd.text(get_disk_usage(disk_path), 2, align='right')
|
|
||||||
time.sleep(5.0)
|
|
||||||
lcd.clear()
|
|
||||||
lcd.text("System Uptime:", 1)
|
|
||||||
lcd.text(get_uptime(), 2, align='right')
|
|
||||||
time.sleep(5.0)
|
|
||||||
|
|
||||||
|
# Disk usage
|
||||||
|
show_two_line("Free Disk:", get_disk_usage(disk_path), 5.0)
|
||||||
|
|
||||||
|
# Uptime
|
||||||
|
show_two_line("System Uptime:", get_uptime(), 5.0)
|
||||||
|
|
||||||
|
# VPN status + time — long idle sleep to reduce CPU usage
|
||||||
lcd.clear()
|
lcd.clear()
|
||||||
lcd.text(" VPN Connected ", 1)
|
lcd.text(" VPN Connected ", 1)
|
||||||
lcd.text(get_system_time().rjust(16-len(get_system_time())), 2, align='left')
|
t = get_system_time()
|
||||||
time.sleep(40.0) #sleep a lot to idle cpu
|
lcd.text(t.rjust(LCD_WIDTH - len(t)), 2, align="left")
|
||||||
|
time.sleep(40.0)
|
||||||
|
|
||||||
#check if wireguard is still connected
|
# Re-check WireGuard connectivity
|
||||||
state_ok = get_wireguard_state(wireguard_interface)
|
state_ok = get_wireguard_state(wireguard_interface)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user