feat: support non-root execution via safe command runner
parent
601ccb355d
commit
74c26427a6
|
|
@ -0,0 +1,88 @@
|
|||
# Running WGDashboard as a non-root service user (advanced)
|
||||
|
||||
WGDashboard can run as a non-root service user if you:
|
||||
- grant controlled `sudo` access for `wg` and `wg-quick`, and
|
||||
- allow read/write access to the WireGuard configuration files.
|
||||
|
||||
This is an advanced setup. Make a backup before changing a live system.
|
||||
|
||||
## 1) Create a service user and group
|
||||
|
||||
```
|
||||
sudo groupadd --system wgdashboard
|
||||
sudo groupadd --system wireguard
|
||||
sudo useradd --system --home-dir /opt/wgdashboard --shell /sbin/nologin \
|
||||
--gid wgdashboard --groups wireguard wgdashboard
|
||||
```
|
||||
|
||||
## 2) Set permissions
|
||||
|
||||
```
|
||||
# WGDashboard app directory
|
||||
sudo chown -R wgdashboard:wgdashboard /opt/wgdashboard/src
|
||||
sudo chmod 0750 /opt/wgdashboard/src
|
||||
sudo chmod 0750 /opt/wgdashboard/src/log /opt/wgdashboard/src/db
|
||||
sudo chmod 0600 /opt/wgdashboard/src/wg-dashboard.ini
|
||||
|
||||
# WireGuard config directory
|
||||
sudo chgrp wireguard /etc/wireguard
|
||||
sudo chmod 0750 /etc/wireguard
|
||||
sudo chgrp wireguard /etc/wireguard/*.conf
|
||||
sudo chmod 0660 /etc/wireguard/*.conf
|
||||
|
||||
# Keep private keys locked to root
|
||||
sudo chown root:root /etc/wireguard/*.key
|
||||
sudo chmod 0600 /etc/wireguard/*.key
|
||||
```
|
||||
|
||||
## 3) Sudoers allowlist (required)
|
||||
|
||||
Create `/etc/sudoers.d/wgdashboard`:
|
||||
|
||||
```
|
||||
Defaults:wgdashboard !requiretty
|
||||
wgdashboard ALL=(root) NOPASSWD: /usr/sbin/wg, /usr/sbin/wg-quick
|
||||
```
|
||||
|
||||
Validate:
|
||||
|
||||
```
|
||||
sudo visudo -cf /etc/sudoers.d/wgdashboard
|
||||
```
|
||||
|
||||
## 4) Systemd override
|
||||
|
||||
Create `/etc/systemd/system/wg-dashboard.service.d/override.conf`:
|
||||
|
||||
```
|
||||
[Service]
|
||||
User=wgdashboard
|
||||
Group=wgdashboard
|
||||
SupplementaryGroups=wireguard
|
||||
ExecStart=
|
||||
ExecStart=/opt/wgdashboard/src/venv/bin/gunicorn --config /opt/wgdashboard/src/gunicorn.conf.py
|
||||
ExecStop=
|
||||
ExecStop=/bin/kill -TERM $MAINPID
|
||||
ExecReload=
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
```
|
||||
|
||||
Then reload and restart:
|
||||
|
||||
```
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart wg-dashboard
|
||||
```
|
||||
|
||||
## 5) Verify
|
||||
|
||||
```
|
||||
systemctl status wg-dashboard
|
||||
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:10819/
|
||||
```
|
||||
|
||||
## Notes
|
||||
- WGDashboard executes `wg` and `wg-quick`. This requires sudo when running as non-root.
|
||||
- If you use AmneziaWG, ensure the corresponding binaries are available.
|
||||
- If you use SELinux/AppArmor, add policy exceptions as needed.
|
||||
|
||||
|
|
@ -5,7 +5,7 @@ import subprocess
|
|||
import uuid
|
||||
|
||||
from .Peer import Peer
|
||||
from .Utilities import ValidateIPAddressesWithRange, ValidateDNSAddress, GenerateWireguardPublicKey
|
||||
from .Utilities import ValidateIPAddressesWithRange, ValidateDNSAddress, GenerateWireguardPublicKey, RunCommand
|
||||
|
||||
|
||||
class AmneziaWGPeer(Peer):
|
||||
|
|
@ -58,16 +58,16 @@ class AmneziaWGPeer(Peer):
|
|||
with open(uid, "w+") as f:
|
||||
f.write(preshared_key)
|
||||
newAllowedIPs = allowed_ip.replace(" ", "")
|
||||
updateAllowedIp = subprocess.check_output(
|
||||
f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
cmd = [self.configuration.Protocol, "set", self.configuration.Name, "peer", self.id, "allowed-ips",
|
||||
newAllowedIPs, "preshared-key", (uid if pskExist else "/dev/null")]
|
||||
updateAllowedIp = RunCommand(cmd, require_root=True)
|
||||
|
||||
if pskExist: os.remove(uid)
|
||||
|
||||
if len(updateAllowedIp.decode().strip("\n")) != 0:
|
||||
return False, "Update peer failed when updating Allowed IPs"
|
||||
saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
saveConfig = RunCommand([f"{self.configuration.Protocol}-quick", "save", self.configuration.Name],
|
||||
require_root=True)
|
||||
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
|
||||
return False, "Update peer failed when saving the configuration"
|
||||
|
||||
|
|
@ -89,4 +89,4 @@ class AmneziaWGPeer(Peer):
|
|||
self.configuration.getPeers()
|
||||
return True, None
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return False, exc.output.decode("UTF-8").strip()
|
||||
return False, exc.output.decode("UTF-8").strip()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from flask import current_app
|
|||
from .PeerJobs import PeerJobs
|
||||
from .AmneziaWGPeer import AmneziaWGPeer
|
||||
from .PeerShareLinks import PeerShareLinks
|
||||
from .Utilities import RegexMatch
|
||||
from .Utilities import RegexMatch, RunCommand
|
||||
from .WireguardConfiguration import WireguardConfiguration
|
||||
from .DashboardWebHooks import DashboardWebHooks
|
||||
|
||||
|
|
@ -293,13 +293,14 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
|||
with open(uid, "w+") as f:
|
||||
f.write(p['preshared_key'])
|
||||
|
||||
subprocess.check_output(
|
||||
f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
cmd = [self.Protocol, "set", self.Name, "peer", p['id'], "allowed-ips",
|
||||
p['allowed_ip'].replace(' ', '')]
|
||||
if presharedKeyExist:
|
||||
cmd += ["preshared-key", uid]
|
||||
RunCommand(cmd, require_root=True)
|
||||
if presharedKeyExist:
|
||||
os.remove(uid)
|
||||
subprocess.check_output(
|
||||
f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||
RunCommand([f"{self.Protocol}-quick", "save", self.Name], require_root=True)
|
||||
self.getPeers()
|
||||
for p in peers:
|
||||
p = self.searchPeer(p['id'])
|
||||
|
|
@ -319,4 +320,4 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
|||
with self.engine.connect() as conn:
|
||||
restricted = conn.execute(self.peersRestrictedTable.select()).mappings().fetchall()
|
||||
for i in restricted:
|
||||
self.RestrictedPeers.append(AmneziaWGPeer(i, self))
|
||||
self.RestrictedPeers.append(AmneziaWGPeer(i, self))
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import jinja2
|
|||
import sqlalchemy as db
|
||||
from .PeerJob import PeerJob
|
||||
from .PeerShareLink import PeerShareLink
|
||||
from .Utilities import GenerateWireguardPublicKey, ValidateIPAddressesWithRange, ValidateDNSAddress
|
||||
from .Utilities import GenerateWireguardPublicKey, ValidateIPAddressesWithRange, ValidateDNSAddress, RunCommand
|
||||
|
||||
|
||||
class Peer:
|
||||
|
|
@ -94,15 +94,15 @@ class Peer:
|
|||
with open(uid, "w+") as f:
|
||||
f.write(preshared_key)
|
||||
newAllowedIPs = allowed_ip.replace(" ", "")
|
||||
updateAllowedIp = subprocess.check_output(
|
||||
f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
cmd = [self.configuration.Protocol, "set", self.configuration.Name, "peer", self.id, "allowed-ips",
|
||||
newAllowedIPs, "preshared-key", (uid if pskExist else "/dev/null")]
|
||||
updateAllowedIp = RunCommand(cmd, require_root=True)
|
||||
|
||||
if pskExist: os.remove(uid)
|
||||
if len(updateAllowedIp.decode().strip("\n")) != 0:
|
||||
return False, "Update peer failed when updating Allowed IPs"
|
||||
saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
saveConfig = RunCommand([f"{self.configuration.Protocol}-quick", "save", self.configuration.Name],
|
||||
require_root=True)
|
||||
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
|
||||
return False, "Update peer failed when saving the configuration"
|
||||
with self.configuration.engine.begin() as conn:
|
||||
|
|
@ -351,4 +351,4 @@ class Peer:
|
|||
|
||||
hours, remainder = divmod(delta.total_seconds(), 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"
|
||||
return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import re, ipaddress
|
||||
import re, ipaddress, os, shutil
|
||||
import subprocess
|
||||
|
||||
|
||||
|
|
@ -68,18 +68,25 @@ def ValidateEndpointAllowedIPs(IPs) -> tuple[bool, str] | tuple[bool, None]:
|
|||
return False, str(e)
|
||||
return True, None
|
||||
|
||||
def RunCommand(cmd: list[str], input_data: bytes | None = None, require_root: bool = False) -> bytes:
|
||||
exe = shutil.which(cmd[0]) if not os.path.isabs(cmd[0]) else cmd[0]
|
||||
if exe:
|
||||
cmd = [exe] + cmd[1:]
|
||||
if require_root and os.geteuid() != 0:
|
||||
sudo_path = shutil.which("sudo") or "/usr/sbin/sudo"
|
||||
cmd = [sudo_path, "--non-interactive"] + cmd
|
||||
return subprocess.check_output(cmd, input=input_data, stderr=subprocess.STDOUT)
|
||||
|
||||
def GenerateWireguardPublicKey(privateKey: str) -> tuple[bool, str] | tuple[bool, None]:
|
||||
try:
|
||||
publicKey = subprocess.check_output(f"wg pubkey", input=privateKey.encode(), shell=True,
|
||||
stderr=subprocess.STDOUT)
|
||||
publicKey = RunCommand(["wg", "pubkey"], input_data=privateKey.encode())
|
||||
return True, publicKey.decode().strip('\n')
|
||||
except subprocess.CalledProcessError:
|
||||
return False, None
|
||||
|
||||
def GenerateWireguardPrivateKey() -> tuple[bool, str] | tuple[bool, None]:
|
||||
try:
|
||||
publicKey = subprocess.check_output(f"wg genkey", shell=True,
|
||||
stderr=subprocess.STDOUT)
|
||||
publicKey = RunCommand(["wg", "genkey"])
|
||||
return True, publicKey.decode().strip('\n')
|
||||
except subprocess.CalledProcessError:
|
||||
return False, None
|
||||
|
|
@ -101,4 +108,4 @@ def ValidatePasswordStrength(password: str) -> tuple[bool, str] | tuple[bool, No
|
|||
if not re.search(r'[$&+,:;=?@#|\'<>.\-^*()%!~_-]', password):
|
||||
return False, "Password must contain at least 1 special character from $&+,:;=?@#|'<>.-^*()%!~_-"
|
||||
|
||||
return True, None
|
||||
return True, None
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from .Peer import Peer
|
|||
from .PeerJobs import PeerJobs
|
||||
from .PeerShareLinks import PeerShareLinks
|
||||
from .Utilities import StringToBoolean, GenerateWireguardPublicKey, RegexMatch, ValidateDNSAddress, \
|
||||
ValidateEndpointAllowedIPs
|
||||
ValidateEndpointAllowedIPs, RunCommand
|
||||
from .WireguardConfigurationInfo import WireguardConfigurationInfo, PeerGroupsClass
|
||||
from .DashboardWebHooks import DashboardWebHooks
|
||||
|
||||
|
|
@ -67,6 +67,8 @@ class WireguardConfiguration:
|
|||
self.engine: sqlalchemy.Engine = sqlalchemy.create_engine(ConnectionString("wgdashboard"))
|
||||
self.metadata: sqlalchemy.MetaData = sqlalchemy.MetaData()
|
||||
self.dbType = self.DashboardConfig.GetConfig("Database", "type")[1]
|
||||
self._realtime_rate_samples: dict[str, dict] = {}
|
||||
self._realtime_rates: dict[str, dict] = {}
|
||||
|
||||
if name is not None:
|
||||
if data is not None and "Backup" in data.keys():
|
||||
|
|
@ -496,6 +498,50 @@ class WireguardConfiguration:
|
|||
"time": datetime.now()
|
||||
})
|
||||
)
|
||||
|
||||
def getPeersDailyUsage(self, peer_ids: list[str], day: datetime.date):
|
||||
if not peer_ids:
|
||||
return {}
|
||||
if not self.configurationInfo.PeerTrafficTracking:
|
||||
return {pid: {"total": 0, "sent": 0, "receive": 0} for pid in peer_ids}
|
||||
|
||||
start = datetime(day.year, day.month, day.day, 0, 0, 0, 0)
|
||||
end = start + timedelta(days=1) - timedelta(microseconds=1)
|
||||
|
||||
total_expr = self.peersTransferTable.c.cumu_data + self.peersTransferTable.c.total_data
|
||||
sent_expr = self.peersTransferTable.c.cumu_sent + self.peersTransferTable.c.total_sent
|
||||
receive_expr = self.peersTransferTable.c.cumu_receive + self.peersTransferTable.c.total_receive
|
||||
|
||||
usage = {pid: {"total": 0, "sent": 0, "receive": 0} for pid in peer_ids}
|
||||
with self.engine.connect() as conn:
|
||||
rows = conn.execute(
|
||||
sqlalchemy.select(
|
||||
self.peersTransferTable.c.id.label("id"),
|
||||
sqlalchemy.func.max(total_expr).label("max_total"),
|
||||
sqlalchemy.func.min(total_expr).label("min_total"),
|
||||
sqlalchemy.func.max(sent_expr).label("max_sent"),
|
||||
sqlalchemy.func.min(sent_expr).label("min_sent"),
|
||||
sqlalchemy.func.max(receive_expr).label("max_receive"),
|
||||
sqlalchemy.func.min(receive_expr).label("min_receive"),
|
||||
).where(
|
||||
sqlalchemy.and_(
|
||||
self.peersTransferTable.c.id.in_(peer_ids),
|
||||
self.peersTransferTable.c.time >= start,
|
||||
self.peersTransferTable.c.time <= end,
|
||||
)
|
||||
).group_by(
|
||||
self.peersTransferTable.c.id
|
||||
)
|
||||
).mappings().fetchall()
|
||||
for row in rows:
|
||||
pid = row["id"]
|
||||
if pid in usage:
|
||||
usage[pid] = {
|
||||
"total": max((row["max_total"] or 0) - (row["min_total"] or 0), 0),
|
||||
"sent": max((row["max_sent"] or 0) - (row["min_sent"] or 0), 0),
|
||||
"receive": max((row["max_receive"] or 0) - (row["min_receive"] or 0), 0),
|
||||
}
|
||||
return usage
|
||||
|
||||
def logPeersHistoryEndpoint(self):
|
||||
with self.engine.begin() as conn:
|
||||
|
|
@ -560,12 +606,14 @@ class WireguardConfiguration:
|
|||
with open(uid, "w+") as f:
|
||||
f.write(p['preshared_key'])
|
||||
|
||||
subprocess.check_output(f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
cmd = [self.Protocol, "set", self.Name, "peer", p['id'], "allowed-ips",
|
||||
p['allowed_ip'].replace(' ', '')]
|
||||
if presharedKeyExist:
|
||||
cmd += ["preshared-key", uid]
|
||||
RunCommand(cmd, require_root=True)
|
||||
if presharedKeyExist:
|
||||
os.remove(uid)
|
||||
subprocess.check_output(
|
||||
f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||
RunCommand([f"{self.Protocol}-quick", "save", self.Name], require_root=True)
|
||||
self.getPeers()
|
||||
for p in peers:
|
||||
p = self.searchPeer(p['id'])
|
||||
|
|
@ -615,8 +663,11 @@ class WireguardConfiguration:
|
|||
with open(uid, "w+") as f:
|
||||
f.write(restrictedPeer['preshared_key'])
|
||||
|
||||
subprocess.check_output(f"{self.Protocol} set {self.Name} peer {restrictedPeer['id']} allowed-ips {restrictedPeer['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
cmd = [self.Protocol, "set", self.Name, "peer", restrictedPeer['id'], "allowed-ips",
|
||||
restrictedPeer['allowed_ip'].replace(' ', '')]
|
||||
if presharedKeyExist:
|
||||
cmd += ["preshared-key", uid]
|
||||
RunCommand(cmd, require_root=True)
|
||||
if presharedKeyExist: os.remove(uid)
|
||||
else:
|
||||
return False, "Failed to allow access of peer " + i
|
||||
|
|
@ -636,8 +687,7 @@ class WireguardConfiguration:
|
|||
found, pf = self.searchPeer(p)
|
||||
if found:
|
||||
try:
|
||||
subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
RunCommand([self.Protocol, "set", self.Name, "peer", pf.id, "remove"], require_root=True)
|
||||
conn.execute(
|
||||
self.peersRestrictedTable.insert().from_select(
|
||||
[c.name for c in self.peersTable.columns],
|
||||
|
|
@ -688,8 +738,7 @@ class WireguardConfiguration:
|
|||
AllPeerShareLinks.updateLinkExpireDate(shareLink.ShareID, datetime.now())
|
||||
if found:
|
||||
try:
|
||||
subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
RunCommand([self.Protocol, "set", self.Name, "peer", pf.id, "remove"], require_root=True)
|
||||
conn.execute(
|
||||
self.peersTable.delete().where(
|
||||
self.peersTable.columns.id == pf.id
|
||||
|
|
@ -719,7 +768,7 @@ class WireguardConfiguration:
|
|||
|
||||
def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]:
|
||||
try:
|
||||
subprocess.check_output(f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||
RunCommand([f"{self.Protocol}-quick", "save", self.Name], require_root=True)
|
||||
return True, None
|
||||
except subprocess.CalledProcessError as e:
|
||||
return False, str(e)
|
||||
|
|
@ -728,8 +777,7 @@ class WireguardConfiguration:
|
|||
if not self.getStatus():
|
||||
self.toggleConfiguration()
|
||||
try:
|
||||
latestHandshake = subprocess.check_output(f"{self.Protocol} show {self.Name} latest-handshakes",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
latestHandshake = RunCommand([self.Protocol, "show", self.Name, "latest-handshakes"], require_root=True)
|
||||
except subprocess.CalledProcessError:
|
||||
return "stopped"
|
||||
latestHandshake = latestHandshake.decode("UTF-8").split()
|
||||
|
|
@ -768,8 +816,7 @@ class WireguardConfiguration:
|
|||
if not self.getStatus():
|
||||
self.toggleConfiguration()
|
||||
# try:
|
||||
data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} transfer",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
data_usage = RunCommand([self.Protocol, "show", self.Name, "transfer"], require_root=True)
|
||||
data_usage = data_usage.decode("UTF-8").split("\n")
|
||||
|
||||
data_usage = [p.split("\t") for p in data_usage]
|
||||
|
|
@ -791,7 +838,11 @@ class WireguardConfiguration:
|
|||
cur_total_receive = float(data_usage[i][1]) / (1024 ** 3)
|
||||
cumulative_receive = cur_i['cumu_receive'] + total_receive
|
||||
cumulative_sent = cur_i['cumu_sent'] + total_sent
|
||||
if total_sent <= cur_total_sent and total_receive <= cur_total_receive:
|
||||
# Allow minor floating-point drift to avoid false rollover.
|
||||
epsilon_gb = 0.0005 # ~0.5 MB
|
||||
sent_diff = cur_total_sent - total_sent
|
||||
recv_diff = cur_total_receive - total_receive
|
||||
if sent_diff >= -epsilon_gb and recv_diff >= -epsilon_gb:
|
||||
total_sent = cur_total_sent
|
||||
total_receive = cur_total_receive
|
||||
else:
|
||||
|
|
@ -821,13 +872,54 @@ class WireguardConfiguration:
|
|||
)
|
||||
|
||||
|
||||
def getPeersRealtimeRates(self):
|
||||
if not self.getStatus():
|
||||
self.toggleConfiguration()
|
||||
try:
|
||||
data_usage = RunCommand([self.Protocol, "show", self.Name, "transfer"], require_root=True)
|
||||
except subprocess.CalledProcessError:
|
||||
return {}
|
||||
now = time.time()
|
||||
data_usage = data_usage.decode("UTF-8").split("\n")
|
||||
data_usage = [p.split("\t") for p in data_usage]
|
||||
rates = {}
|
||||
for row in data_usage:
|
||||
if len(row) == 3:
|
||||
peer_id = row[0]
|
||||
recv_bytes = float(row[1])
|
||||
sent_bytes = float(row[2])
|
||||
last = self._realtime_rate_samples.get(peer_id)
|
||||
sent_bps = 0.0
|
||||
recv_bps = 0.0
|
||||
if last:
|
||||
dt = now - last.get("ts", now)
|
||||
if dt > 0:
|
||||
sent_delta = sent_bytes - last.get("sent", sent_bytes)
|
||||
recv_delta = recv_bytes - last.get("recv", recv_bytes)
|
||||
if sent_delta < 0:
|
||||
sent_delta = 0
|
||||
if recv_delta < 0:
|
||||
recv_delta = 0
|
||||
sent_bps = sent_delta / dt
|
||||
recv_bps = recv_delta / dt
|
||||
rates[peer_id] = {
|
||||
"sent_bps": sent_bps,
|
||||
"recv_bps": recv_bps,
|
||||
"updated_at": now
|
||||
}
|
||||
self._realtime_rate_samples[peer_id] = {
|
||||
"sent": sent_bytes,
|
||||
"recv": recv_bytes,
|
||||
"ts": now
|
||||
}
|
||||
self._realtime_rates = rates
|
||||
return rates
|
||||
|
||||
def getPeersEndpoint(self):
|
||||
if not self.getStatus():
|
||||
self.toggleConfiguration()
|
||||
try:
|
||||
data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} endpoints",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
data_usage = RunCommand([self.Protocol, "show", self.Name, "endpoints"], require_root=True)
|
||||
except subprocess.CalledProcessError:
|
||||
return "stopped"
|
||||
data_usage = data_usage.decode("UTF-8").split()
|
||||
|
|
@ -847,14 +939,13 @@ class WireguardConfiguration:
|
|||
self.getStatus()
|
||||
if self.Status:
|
||||
try:
|
||||
check = subprocess.check_output(f"{self.Protocol}-quick down {self.Name}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
check = RunCommand([f"{self.Protocol}-quick", "down", self.Name], require_root=True)
|
||||
self.removeAutostart()
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return False, str(exc.output.strip().decode("utf-8"))
|
||||
else:
|
||||
try:
|
||||
check = subprocess.check_output(f"{self.Protocol}-quick up {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||
check = RunCommand([f"{self.Protocol}-quick", "up", self.Name], require_root=True)
|
||||
self.addAutostart()
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return False, str(exc.output.strip().decode("utf-8"))
|
||||
|
|
@ -1291,4 +1382,4 @@ class WireguardConfiguration:
|
|||
conn.execute(sqlalchemy.text('VACUUM;'))
|
||||
except Exception as e:
|
||||
return False
|
||||
return True
|
||||
return True
|
||||
|
|
|
|||
Loading…
Reference in New Issue