From b2e0db146df7eb3caf6dfa53f3dd6224f73ed9c4 Mon Sep 17 00:00:00 2001 From: ayurishchev Date: Thu, 21 May 2026 09:58:25 +0300 Subject: [PATCH] Initial commit --- config | 20 ++ data/clients.json | 1 + profiler.sh | 513 ++++++++++++++++++++++++++++++++++++++++++++++ reade.md | 70 +++++++ 4 files changed, 604 insertions(+) create mode 100644 config create mode 100644 data/clients.json create mode 100755 profiler.sh create mode 100644 reade.md diff --git a/config b/config new file mode 100644 index 0000000..54fa87c --- /dev/null +++ b/config @@ -0,0 +1,20 @@ +SERVER_INTERFACE=wg1 + +SERVER_PRIVATE_KEY=XXXXXNSdlduS3gUedODh8ngG04kZujKN1VGNhhXXXXX= +SERVER_PUBLIC_KEY=XXXXX200yQjOvkJDLdZgrK4tTE60Nu1C8X9YrmXXXXX= + +SERVER_PUBLIC_IP=1.2.3.4 +SERVER_PUBLIC_PORT=27538 + +SERVER_NETWORK=10.101.0.0/24 +SERVER_ADDRESS=10.101.0.1/24 + +DNS_SERVER=1.1.1.1 + +SERVER_MTU=1400 + +SERVER_CONFIG_PATH=/etc/wireguard/wg1.conf + +MT_HOST=1.2.3.4 +MT_USER=SomeLogin +MT_PASSW=SomePassword diff --git a/data/clients.json b/data/clients.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/data/clients.json @@ -0,0 +1 @@ +[] diff --git a/profiler.sh b/profiler.sh new file mode 100755 index 0000000..f100e91 --- /dev/null +++ b/profiler.sh @@ -0,0 +1,513 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +CONFIG_FILE="${SCRIPT_DIR}/config" +DATA_DIR="${SCRIPT_DIR}/data" +CLIENT_DIR="${SCRIPT_DIR}/clients" +REGISTRY="${DATA_DIR}/clients.json" + +die() { + printf "ERROR: %s\n" "$*" >&2 + exit 1 +} + +info() { + printf "INFO: %s\n" "$*" +} + +require_binary() { + command -v "$1" >/dev/null 2>&1 \ + || die "$1 not installed" +} + +load_config() { + [[ -f "$CONFIG_FILE" ]] || die "Config not found" + # shellcheck source=/dev/null + source "$CONFIG_FILE" +} + +init_storage() { + + mkdir -p "$DATA_DIR" + mkdir -p "$CLIENT_DIR" + + if [[ ! -f "$REGISTRY" ]]; then + echo "[]" > "$REGISTRY" + fi + + # Проверка валидности JSON + jq empty "$REGISTRY" \ + || die "Registry JSON is invalid" +} + +sanitize() { + local v="$1" + v="${v//[^a-zA-Z0-9._-]/_}" + printf "%s" "$v" +} + +get_next_id() { + + local id + + id="$(jq -r ' + if length == 0 then 1 + else (map(.id) | max) + 1 + end + ' "$REGISTRY")" + + [[ "$id" =~ ^[0-9]+$ ]] \ + || die "Failed to calculate next ID" + + echo "$id" +} + +get_last_ip() { + + jq -r ' + if length == 0 then empty + else (sort_by(.id) | last.ip) + end + ' "$REGISTRY" +} + +get_first_client_ip() { + + local network="${SERVER_NETWORK}" + + # Удаляем CIDR suffix + network="${network%%/*}" + + IFS='.' read -r o1 o2 o3 o4 <<< "$network" + + # Первый клиент всегда .2 + echo "${o1}.${o2}.${o3}.2" +} + +increment_ip() { + + local current_ip="$1" + + # Если клиентов еще нет — + # выдаем первый IP из SERVER_NETWORK + if [[ -z "$current_ip" ]]; then + get_first_client_ip + return + fi + + IFS='.' read -r o1 o2 o3 o4 <<< "$current_ip" + + ((o4++)) + + # Защита от выхода за пределы /24 + if ((o4 >= 255)); then + die "IP pool exhausted" + fi + + echo "${o1}.${o2}.${o3}.${o4}" +} + +generate_client_keys() { + + CLIENT_PRIV="$(wg genkey)" + + CLIENT_PUB="$( + printf "%s" "$CLIENT_PRIV" | wg pubkey + )" + + CLIENT_PSK="$(wg genpsk)" +} + +create_client_config() { + + local id="$1" + local name="$2" + local ip="$3" + + local filename="${CLIENT_DIR}/${name}.conf" + + cat > "$filename" < "$tmp_file" \ + || die "Failed to update registry" + + mv "$tmp_file" "$REGISTRY" + + # Проверка после записи + jq empty "$REGISTRY" \ + || die "Registry corrupted after write" +} + +create_client() { + + local raw_name="" + local apply_ros=false + + while [[ $# -gt 0 ]]; do + case "$1" in + --apply_ros) + apply_ros=true + shift + ;; + *) + if [[ -z "$raw_name" ]]; then + raw_name="$1" + else + die "Unexpected argument: $1" + fi + shift + ;; + esac + done + + [[ -n "$raw_name" ]] \ + || die "Client name required" + + local name + name="$(sanitize "$raw_name")" + + [[ -n "$name" ]] \ + || die "Sanitized name is empty" + + local id + id="$(get_next_id)" + + local last_ip + last_ip="$(get_last_ip)" + + local next_ip + next_ip="$(increment_ip "$last_ip")" + + generate_client_keys + + create_client_config "$id" "$name" "$next_ip" + + registry_add "$id" "$name" "$next_ip" + + info "Client registered: id=$id name=$name ip=$next_ip" + + print_mikrotik_commands + + info "RouterOS Configuration Script has been Created: id=$id name=$name" + + if [[ "$apply_ros" == true ]]; then + apply_mikrotik_commands + info "RouterOS WG Peer has been Configured: id=$id name=$name" + else + info "RouterOS WG Peer was not Configured (use --apply_ros to apply configuration)" + fi + +} + +list_clients() { + + jq -r ' + if length == 0 then + "No clients" + else + .[] | "ID=\(.id) NAME=\(.name) IP=\(.ip) STATUS=\(.is_enabled)" + end + ' "$REGISTRY" +} + +disable_client() { + + local id="${1:-}" + + [[ "$id" =~ ^[0-9]+$ ]] \ + || die "Invalid ID" + + local current_status name + + read -r current_status name < <(jq -r --argjson id "$id" '.[] | select(.id == $id) | "\(.is_enabled) \(.name)"' "$REGISTRY" 2>/dev/null) + + if [[ -z "$name" ]]; then + die "Client with ID $id not found" + fi + + local new_status + if [[ "$current_status" == "ACTIVE" ]]; then + new_status="DISABLED" + else + new_status="ACTIVE" + fi + + local tmp_file + tmp_file="$(mktemp)" + + jq \ + --argjson id "$id" \ + --arg new_status "$new_status" \ + ' + map( + if .id == $id then + .is_enabled = $new_status + else + . + end + ) + ' "$REGISTRY" > "$tmp_file" \ + || { rm -f "$tmp_file"; die "Failed to update registry status"; } + + mv "$tmp_file" "$REGISTRY" + + info "Client $id ($name) status changed from $current_status to $new_status" + +} + +enable_client() { + + local id="${1:-}" + + [[ "$id" =~ ^[0-9]+$ ]] \ + || die "Invalid ID" + + local current_status name + + read -r current_status name < <(jq -r --argjson id "$id" '.[] | select(.id == $id) | "\(.is_enabled) \(.name)"' "$REGISTRY" 2>/dev/null) + + if [[ -z "$name" ]]; then + die "Client with ID $id not found" + fi + + local new_status + if [[ "$current_status" == "DISABLED" ]]; then + new_status="ACTIVE" + else + new_status="DISABLED" + fi + + local tmp_file + tmp_file="$(mktemp)" + + jq \ + --argjson id "$id" \ + --arg new_status "$new_status" \ + ' + map( + if .id == $id then + .is_enabled = $new_status + else + . + end + ) + ' "$REGISTRY" > "$tmp_file" \ + || { rm -f "$tmp_file"; die "Failed to update registry status"; } + + mv "$tmp_file" "$REGISTRY" + + info "Client $id ($name) status changed from $current_status to $new_status" + +} + +delete_client() { + local id="${1:-}" + + [[ "$id" =~ ^[0-9]+$ ]] \ + || die "Invalid ID" + + local name + name="$(jq -r --argjson id "$id" '.[] | select(.id == $id) | .name' "$REGISTRY" 2>/dev/null)" + + if [[ -z "$name" ]]; then + die "Client with ID $id not found" + fi + + local tmp_file + tmp_file="$(mktemp)" + + jq \ + --argjson id "$id" \ + 'map(select(.id != $id))' \ + "$REGISTRY" > "$tmp_file" \ + || { rm -f "$tmp_file"; die "Failed to update registry"; } + + mv "$tmp_file" "$REGISTRY" + + if [[ -n "$name" ]]; then + rm -f "${CLIENT_DIR}/${name}.conf" + rm -f "${CLIENT_DIR}/${name}.png" + rm -f "${CLIENT_DIR}/${name}.rsc" + fi + + info "Client $id ($name) removed" +} + +print_mikrotik_commands() { + client_ip=$(jq -r --arg val "$name" '.[] | select(.name == $val) | .ip' "$REGISTRY") + + local mt_filename="${CLIENT_DIR}/${name}.rsc" + + cat > "$mt_filename" <