GUI: add enable/disable/delete peer actions and sync script-added peers
This commit is contained in:
124
gui/app.py
124
gui/app.py
@@ -40,12 +40,21 @@ def ensure_schema():
|
|||||||
client_address TEXT,
|
client_address TEXT,
|
||||||
advertised_routes TEXT,
|
advertised_routes TEXT,
|
||||||
client_conf TEXT,
|
client_conf TEXT,
|
||||||
|
peer_psk TEXT,
|
||||||
|
peer_allowed_ips TEXT,
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
""")
|
""")
|
||||||
cols = {row[1] for row in cur.execute("PRAGMA table_info(peers)").fetchall()}
|
cols = {row[1] for row in cur.execute("PRAGMA table_info(peers)").fetchall()}
|
||||||
if "client_conf" not in cols:
|
if "client_conf" not in cols:
|
||||||
cur.execute("ALTER TABLE peers ADD COLUMN client_conf TEXT")
|
cur.execute("ALTER TABLE peers ADD COLUMN client_conf TEXT")
|
||||||
|
if "peer_psk" not in cols:
|
||||||
|
cur.execute("ALTER TABLE peers ADD COLUMN peer_psk TEXT")
|
||||||
|
if "peer_allowed_ips" not in cols:
|
||||||
|
cur.execute("ALTER TABLE peers ADD COLUMN peer_allowed_ips TEXT")
|
||||||
|
if "enabled" not in cols:
|
||||||
|
cur.execute("ALTER TABLE peers ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
@@ -282,13 +291,13 @@ def new_peer():
|
|||||||
with db_conn() as conn:
|
with db_conn() as conn:
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"UPDATE peers SET name=?, client_address=?, advertised_routes=?, client_conf=? WHERE public_key=?",
|
"UPDATE peers SET name=?, client_address=?, advertised_routes=?, client_conf=?, peer_psk=?, peer_allowed_ips=?, enabled=1 WHERE public_key=?",
|
||||||
(name, client_addr, routes, client_conf, client_pub),
|
(name, client_addr, routes, client_conf, client_psk, client_addr + (("," + routes) if routes else ""), client_pub),
|
||||||
)
|
)
|
||||||
if cur.rowcount == 0:
|
if cur.rowcount == 0:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT INTO peers(name, public_key, client_address, advertised_routes, client_conf) VALUES (?,?,?,?,?)",
|
"INSERT INTO peers(name, public_key, client_address, advertised_routes, client_conf, peer_psk, peer_allowed_ips, enabled) VALUES (?,?,?,?,?,?,?,1)",
|
||||||
(name, client_pub, client_addr, routes, client_conf),
|
(name, client_pub, client_addr, routes, client_conf, client_psk, client_addr + (("," + routes) if routes else "")),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
@@ -327,6 +336,113 @@ def peer_view(peer_id: int):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/peers/<int:peer_id>/disable")
|
||||||
|
def peer_disable(peer_id: int):
|
||||||
|
with db_conn() as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT * FROM peers WHERE id = ?", (peer_id,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row:
|
||||||
|
flash("Клиент не найден", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
item = dict(row)
|
||||||
|
|
||||||
|
pk = item.get("public_key", "")
|
||||||
|
if not pk:
|
||||||
|
flash("Не найден public key", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
run(["/usr/local/sbin/wg-peerctl", "remove", "--client-public-key", pk])
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
flash(f"Не удалось отключить peer: {e}", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
with db_conn() as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("UPDATE peers SET enabled=0 WHERE id = ?", (peer_id,))
|
||||||
|
conn.commit()
|
||||||
|
flash("Peer отключен", "ok")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/peers/<int:peer_id>/enable")
|
||||||
|
def peer_enable(peer_id: int):
|
||||||
|
with db_conn() as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT * FROM peers WHERE id = ?", (peer_id,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row:
|
||||||
|
flash("Клиент не найден", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
item = dict(row)
|
||||||
|
|
||||||
|
name = item.get("name", "")
|
||||||
|
pk = item.get("public_key", "")
|
||||||
|
addr = item.get("client_address", "")
|
||||||
|
routes = item.get("advertised_routes", "") or ""
|
||||||
|
psk = item.get("peer_psk", "") or ""
|
||||||
|
if not (name and pk and addr and psk):
|
||||||
|
flash("Недостаточно данных для включения peer (нужны name/public key/address/psk)", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"/usr/local/sbin/wg-peerctl",
|
||||||
|
"add",
|
||||||
|
"--client-name",
|
||||||
|
name,
|
||||||
|
"--client-public-key",
|
||||||
|
pk,
|
||||||
|
"--client-address",
|
||||||
|
addr,
|
||||||
|
"--client-preshared-key",
|
||||||
|
psk,
|
||||||
|
"--persistent-keepalive",
|
||||||
|
"25",
|
||||||
|
]
|
||||||
|
if routes:
|
||||||
|
cmd += ["--client-routes", routes]
|
||||||
|
|
||||||
|
try:
|
||||||
|
run(cmd)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
flash(f"Не удалось включить peer: {e}", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
with db_conn() as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("UPDATE peers SET enabled=1 WHERE id = ?", (peer_id,))
|
||||||
|
conn.commit()
|
||||||
|
flash("Peer включен", "ok")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/peers/<int:peer_id>/delete")
|
||||||
|
def peer_delete(peer_id: int):
|
||||||
|
with db_conn() as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT * FROM peers WHERE id = ?", (peer_id,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row:
|
||||||
|
flash("Клиент не найден", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
item = dict(row)
|
||||||
|
|
||||||
|
pk = item.get("public_key", "")
|
||||||
|
if pk:
|
||||||
|
try:
|
||||||
|
run(["/usr/local/sbin/wg-peerctl", "remove", "--client-public-key", pk])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
with db_conn() as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("DELETE FROM peers WHERE id = ?", (peer_id,))
|
||||||
|
conn.commit()
|
||||||
|
flash("Peer удален", "ok")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/scripts")
|
@app.route("/scripts")
|
||||||
def scripts():
|
def scripts():
|
||||||
commands = {
|
commands = {
|
||||||
|
|||||||
@@ -24,6 +24,18 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if p.id %}
|
{% if p.id %}
|
||||||
<a href="{{ url_for('peer_view', peer_id=p.id) }}">QR/Config</a>
|
<a href="{{ url_for('peer_view', peer_id=p.id) }}">QR/Config</a>
|
||||||
|
{% if p.status == 'online' %}
|
||||||
|
<form method="post" action="{{ url_for('peer_disable', peer_id=p.id) }}" style="display:inline">
|
||||||
|
<button type="submit">Отключить</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<form method="post" action="{{ url_for('peer_enable', peer_id=p.id) }}" style="display:inline">
|
||||||
|
<button type="submit">Включить</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
<form method="post" action="{{ url_for('peer_delete', peer_id=p.id) }}" style="display:inline" onsubmit="return confirm('Удалить peer?')">
|
||||||
|
<button type="submit">Удалить</button>
|
||||||
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ fi
|
|||||||
|
|
||||||
LOG_FILE="/var/log/wireguard-peerctl.log"
|
LOG_FILE="/var/log/wireguard-peerctl.log"
|
||||||
WG_META_FILE="/etc/wireguard/wg-meta.env"
|
WG_META_FILE="/etc/wireguard/wg-meta.env"
|
||||||
|
GUI_DB_FILE="/opt/wg-admin-gui/data/wgadmin.db"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'USAGE'
|
cat <<'USAGE'
|
||||||
@@ -27,12 +28,89 @@ usage() {
|
|||||||
[--client-preshared-key <psk>] \
|
[--client-preshared-key <psk>] \
|
||||||
[--persistent-keepalive 25]
|
[--persistent-keepalive 25]
|
||||||
|
|
||||||
|
wg-peerctl.sh remove \
|
||||||
|
--client-public-key <pubkey>
|
||||||
|
|
||||||
Описание:
|
Описание:
|
||||||
Скрипт добавляет peer в конфигурацию WireGuard-сервера идемпотентно.
|
Скрипт добавляет peer в конфигурацию WireGuard-сервера идемпотентно.
|
||||||
Если peer с таким public key уже существует, повторно не добавляет.
|
Если peer с таким public key уже существует, повторно не добавляет.
|
||||||
USAGE
|
USAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sql_escape() {
|
||||||
|
local s="$1"
|
||||||
|
s="${s//\'/\'\'}"
|
||||||
|
printf "%s" "$s"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_gui_db_schema() {
|
||||||
|
command -v sqlite3 >/dev/null 2>&1 || return 0
|
||||||
|
[[ -f "$GUI_DB_FILE" ]] || return 0
|
||||||
|
sqlite3 "$GUI_DB_FILE" <<'SQL' >/dev/null 2>&1 || true
|
||||||
|
CREATE TABLE IF NOT EXISTS peers (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
public_key TEXT UNIQUE NOT NULL,
|
||||||
|
client_address TEXT,
|
||||||
|
advertised_routes TEXT,
|
||||||
|
client_conf TEXT,
|
||||||
|
peer_psk TEXT,
|
||||||
|
peer_allowed_ips TEXT,
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
ALTER TABLE peers ADD COLUMN client_conf TEXT;
|
||||||
|
ALTER TABLE peers ADD COLUMN peer_psk TEXT;
|
||||||
|
ALTER TABLE peers ADD COLUMN peer_allowed_ips TEXT;
|
||||||
|
ALTER TABLE peers ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1;
|
||||||
|
SQL
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_gui_db_upsert_peer() {
|
||||||
|
local name="$1"
|
||||||
|
local pubkey="$2"
|
||||||
|
local address="$3"
|
||||||
|
local routes="$4"
|
||||||
|
local psk="$5"
|
||||||
|
local peer_allowed_ips="$6"
|
||||||
|
local enabled="${7:-1}"
|
||||||
|
|
||||||
|
command -v sqlite3 >/dev/null 2>&1 || return 0
|
||||||
|
[[ -f "$GUI_DB_FILE" ]] || return 0
|
||||||
|
ensure_gui_db_schema
|
||||||
|
|
||||||
|
local e_name e_pub e_addr e_routes e_psk e_allowed
|
||||||
|
e_name="$(sql_escape "$name")"
|
||||||
|
e_pub="$(sql_escape "$pubkey")"
|
||||||
|
e_addr="$(sql_escape "$address")"
|
||||||
|
e_routes="$(sql_escape "$routes")"
|
||||||
|
e_psk="$(sql_escape "$psk")"
|
||||||
|
e_allowed="$(sql_escape "$peer_allowed_ips")"
|
||||||
|
|
||||||
|
sqlite3 "$GUI_DB_FILE" <<SQL >/dev/null 2>&1 || true
|
||||||
|
INSERT INTO peers(name, public_key, client_address, advertised_routes, peer_psk, peer_allowed_ips, enabled)
|
||||||
|
VALUES ('$e_name', '$e_pub', '$e_addr', '$e_routes', '$e_psk', '$e_allowed', $enabled)
|
||||||
|
ON CONFLICT(public_key)
|
||||||
|
DO UPDATE SET
|
||||||
|
name=excluded.name,
|
||||||
|
client_address=excluded.client_address,
|
||||||
|
advertised_routes=excluded.advertised_routes,
|
||||||
|
peer_psk=excluded.peer_psk,
|
||||||
|
peer_allowed_ips=excluded.peer_allowed_ips,
|
||||||
|
enabled=excluded.enabled;
|
||||||
|
SQL
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_gui_db_set_enabled() {
|
||||||
|
local pubkey="$1"
|
||||||
|
local enabled="$2"
|
||||||
|
command -v sqlite3 >/dev/null 2>&1 || return 0
|
||||||
|
[[ -f "$GUI_DB_FILE" ]] || return 0
|
||||||
|
local e_pub
|
||||||
|
e_pub="$(sql_escape "$pubkey")"
|
||||||
|
sqlite3 "$GUI_DB_FILE" "UPDATE peers SET enabled=${enabled} WHERE public_key='${e_pub}';" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
load_meta() {
|
load_meta() {
|
||||||
[[ -f "$WG_META_FILE" ]] || die "Не найден $WG_META_FILE. Сначала выполните install_server.sh"
|
[[ -f "$WG_META_FILE" ]] || die "Не найден $WG_META_FILE. Сначала выполните install_server.sh"
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
@@ -219,6 +297,7 @@ EOF_OUT
|
|||||||
} >> "$WG_CONF"
|
} >> "$WG_CONF"
|
||||||
|
|
||||||
apply_config
|
apply_config
|
||||||
|
sync_gui_db_upsert_peer "$client_name" "$client_pubkey" "$client_address" "$client_routes" "$client_psk" "$peer_allowed_ips" 1
|
||||||
|
|
||||||
cat <<EOF_OUT
|
cat <<EOF_OUT
|
||||||
STATUS=created
|
STATUS=created
|
||||||
@@ -232,6 +311,60 @@ WG_NETWORK=${WG_NETWORK}
|
|||||||
EOF_OUT
|
EOF_OUT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd_remove() {
|
||||||
|
local client_pubkey=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--client-public-key)
|
||||||
|
client_pubkey="$2"; shift 2 ;;
|
||||||
|
*)
|
||||||
|
die "Неизвестный аргумент: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[[ -n "$client_pubkey" ]] || die "Не указан --client-public-key"
|
||||||
|
|
||||||
|
load_meta
|
||||||
|
[[ -f "$WG_CONF" ]] || die "Не найден конфиг WireGuard: $WG_CONF"
|
||||||
|
backup_file "$WG_CONF"
|
||||||
|
|
||||||
|
local tmp
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
awk -v pk="$client_pubkey" '
|
||||||
|
BEGIN {in=0; block=""; keep=1}
|
||||||
|
/^\[Peer\]/ {
|
||||||
|
if (in && keep) printf "%s", block
|
||||||
|
in=1; block=$0 ORS; keep=1; next
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (in) {
|
||||||
|
block = block $0 ORS
|
||||||
|
if ($0 ~ /^PublicKey[[:space:]]*=/) {
|
||||||
|
line=$0
|
||||||
|
sub(/^[^=]*=[[:space:]]*/, "", line)
|
||||||
|
if (line == pk) keep=0
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
print
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (in && keep) printf "%s", block
|
||||||
|
}
|
||||||
|
' "$WG_CONF" > "$tmp"
|
||||||
|
mv "$tmp" "$WG_CONF"
|
||||||
|
safe_chmod_600 "$WG_CONF"
|
||||||
|
|
||||||
|
apply_config
|
||||||
|
sync_gui_db_set_enabled "$client_pubkey" 0
|
||||||
|
|
||||||
|
cat <<EOF_OUT
|
||||||
|
STATUS=removed
|
||||||
|
PUBLIC_KEY=${client_pubkey}
|
||||||
|
WG_INTERFACE=${WG_INTERFACE}
|
||||||
|
EOF_OUT
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
local cmd="${1:-}"
|
local cmd="${1:-}"
|
||||||
if [[ -z "$cmd" ]]; then
|
if [[ -z "$cmd" ]]; then
|
||||||
@@ -246,6 +379,11 @@ main() {
|
|||||||
check_os_supported
|
check_os_supported
|
||||||
cmd_add "$@"
|
cmd_add "$@"
|
||||||
;;
|
;;
|
||||||
|
remove)
|
||||||
|
require_root
|
||||||
|
check_os_supported
|
||||||
|
cmd_remove "$@"
|
||||||
|
;;
|
||||||
-h|--help|help)
|
-h|--help|help)
|
||||||
usage
|
usage
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user