feat(clients): add transactional client creation
This commit is contained in:
+458
-12
@@ -54,7 +54,7 @@ print_usage() {
|
||||
Использование:
|
||||
wg-manager.sh help
|
||||
wg-manager.sh version
|
||||
wg-manager.sh {status|list|add|show|config|qr|remove|diagnose|logs|backups|restore|tui}
|
||||
wg-manager.sh {status|list|add NAME [--mode MODE] [--endpoint HOST]|show|config|qr|remove|diagnose|logs|backups|restore|tui}
|
||||
|
||||
Команды управления WireGuard будут добавлены в следующих версиях.
|
||||
USAGE
|
||||
@@ -76,6 +76,11 @@ debug() {
|
||||
return 0
|
||||
}
|
||||
|
||||
add_failure() {
|
||||
local status=$1 message=$2
|
||||
die "$status" "Добавление клиента: ${message}"
|
||||
}
|
||||
|
||||
trim_space() {
|
||||
local value=$1
|
||||
value="${value#${value%%[!$' \t']*}}"
|
||||
@@ -245,7 +250,7 @@ validate_config_values() {
|
||||
is_uint "$START_HOST" && is_uint "$END_HOST" && (( 10#$START_HOST >= 1 && 10#$START_HOST <= 254 && 10#$END_HOST >= 1 && 10#$END_HOST <= 254 && 10#$START_HOST <= 10#$END_HOST )) || { die "$EXIT_VALIDATION" 'Некорректный диапазон адресов клиентов'; return $?; }
|
||||
server_host=${SERVER_VPN_IP##*.}; (( 10#$START_HOST > 10#$server_host || 10#$server_host > 10#$END_HOST )) || { die "$EXIT_VALIDATION" 'Диапазон клиентов содержит адрес сервера'; return $?; }
|
||||
[[ $USE_IPV6 == 0 || $USE_IPV6 == 1 ]] || { die "$EXIT_VALIDATION" 'USE_IPV6 должен быть 0 или 1'; return $?; }
|
||||
validate_ipv6 "$IPV6_PREFIX" || { die "$EXIT_VALIDATION" 'Некорректный IPv6-префикс'; return $?; }
|
||||
[[ $IPV6_PREFIX == *'::' && $IPV6_PREFIX != *'::'*'::'* ]] && validate_ipv6 "$IPV6_PREFIX" || { die "$EXIT_VALIDATION" 'Некорректный IPv6-префикс'; return $?; }
|
||||
[[ $DEFAULT_CLIENT_MODE == full-tunnel || $DEFAULT_CLIENT_MODE == split-tunnel ]] || { die "$EXIT_VALIDATION" 'Некорректный режим клиента'; return $?; }
|
||||
is_uint "$BACKUP_RETENTION" && (( 10#$BACKUP_RETENTION >= 0 && 10#$BACKUP_RETENTION <= 1000 )) || { die "$EXIT_VALIDATION" 'Некорректное число резервных копий'; return $?; }
|
||||
is_uint "$RECENT_HANDSHAKE_SECONDS" && (( 10#$RECENT_HANDSHAKE_SECONDS <= 604800 )) || { die "$EXIT_VALIDATION" 'Некорректный срок handshake'; return $?; }
|
||||
@@ -563,10 +568,27 @@ load_client_model() {
|
||||
# path. It must write the complete proposed config and return nonzero to abort.
|
||||
TRANSACTION_TEMP_PATHS=()
|
||||
TRANSACTION_BACKUP_PATH=''
|
||||
TRANSACTION_FINALIZER=''
|
||||
TRANSACTION_OWNED_PATHS=()
|
||||
TRANSACTION_PENDING_SIGNAL=''
|
||||
TRANSACTION_ROLLBACK_ACTIVE=0
|
||||
TRANSACTION_ROLLBACK_INTERRUPTED=0
|
||||
TRANSACTION_ROLLBACK_SIGNAL=''
|
||||
|
||||
transaction_test_hooks_enabled() {
|
||||
[[ ${WG_MANAGER_TEST_MODE:-0} == 1 && -n ${TEST_ROOT:-} && $TEST_ROOT == /* ]] || return 1
|
||||
is_control_free "$TEST_ROOT" && path_components_safe "$TEST_ROOT"
|
||||
local path config_path
|
||||
[[ ${WG_MANAGER_TEST_MODE:-0} == 1 && -n ${TEST_ROOT:-} && $TEST_ROOT != / && $TEST_ROOT == /* && -d $TEST_ROOT && ! -L $TEST_ROOT ]] || return 1
|
||||
is_control_free "$TEST_ROOT" && path_components_safe "$TEST_ROOT" || return 1
|
||||
config_path=${CONFIG_PATH:-${WG_MANAGER_CONFIG:-}}
|
||||
[[ -n $config_path && $config_path == "${TEST_ROOT%/}"/* ]] || return 1
|
||||
path_components_safe "$config_path" || return 1
|
||||
for path in "${WG_CONF:-}" "${CLIENT_DIR:-}" "${METADATA_DIR:-}" "${BACKUP_DIR:-}" "${LOCK_FILE:-}"; do
|
||||
[[ -n $path && $path == "${TEST_ROOT%/}"/* ]] && path_components_safe "$path" || return 1
|
||||
done
|
||||
for path in "${ADD_PUBLISH_READY_FIFO:-}" "${ADD_PUBLISH_RELEASE_FIFO:-}" "${ADD_PUBLISH_PID_FILE:-}"; do
|
||||
[[ -z $path ]] && continue
|
||||
[[ $path == "${TEST_ROOT%/}"/* ]] && is_control_free "$path" && path_components_safe "$path" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
transaction_cleanup() {
|
||||
@@ -578,15 +600,27 @@ transaction_cleanup() {
|
||||
fi
|
||||
fi
|
||||
done
|
||||
for path in "${TRANSACTION_OWNED_PATHS[@]:-}"; do
|
||||
if [[ -n "$path" && ( -e "$path" || -L "$path" ) ]]; then
|
||||
if ! rm -f -- "$path"; then
|
||||
printf 'Предупреждение: не удалось удалить опубликованный временный файл\n' >&2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
TRANSACTION_TEMP_PATHS=()
|
||||
TRANSACTION_OWNED_PATHS=()
|
||||
return "$incoming_status"
|
||||
}
|
||||
|
||||
transaction_signal() {
|
||||
local signal=$1
|
||||
transaction_cleanup
|
||||
printf 'Ошибка: операция прервана сигналом %s\n' "$signal" >&2
|
||||
exit "$EXIT_OPERATION"
|
||||
if (( TRANSACTION_ROLLBACK_ACTIVE == 1 )); then
|
||||
TRANSACTION_ROLLBACK_INTERRUPTED=1
|
||||
TRANSACTION_ROLLBACK_SIGNAL=$signal
|
||||
else
|
||||
TRANSACTION_PENDING_SIGNAL=$signal
|
||||
fi
|
||||
return 130
|
||||
}
|
||||
|
||||
TRANSACTION_CREATED_TEMP=''
|
||||
@@ -810,10 +844,15 @@ run_transaction_core() {
|
||||
local transaction_path
|
||||
shift 3
|
||||
TRANSACTION_TEMP_PATHS=(); TRANSACTION_BACKUP_PATH=''
|
||||
TRANSACTION_OWNED_PATHS=(); TRANSACTION_PENDING_SIGNAL=''; TRANSACTION_ROLLBACK_ACTIVE=0; TRANSACTION_ROLLBACK_INTERRUPTED=0; TRANSACTION_ROLLBACK_SIGNAL=''
|
||||
[[ $operation =~ ^[A-Za-z][A-Za-z0-9._:-]{0,63}$ && $target =~ ^[A-Za-z][A-Za-z0-9._:/-]{0,127}$ ]] || { die "$EXIT_VALIDATION" 'Некорректная операция или цель транзакции'; return $?; }
|
||||
is_control_free "$operation" && is_control_free "$target" || { die "$EXIT_VALIDATION" 'Некорректная операция или цель транзакции'; return $?; }
|
||||
[[ $builder =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || { die "$EXIT_VALIDATION" 'Некорректный builder транзакции'; return $?; }
|
||||
declare -F "$builder" >/dev/null 2>&1 || { die "$EXIT_VALIDATION" 'Builder транзакции не является функцией'; return $?; }
|
||||
if [[ -n ${TRANSACTION_FINALIZER:-} ]]; then
|
||||
[[ $TRANSACTION_FINALIZER =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || { die "$EXIT_VALIDATION" 'Некорректный finalizer транзакции'; return $?; }
|
||||
declare -F "$TRANSACTION_FINALIZER" >/dev/null 2>&1 || { die "$EXIT_VALIDATION" 'Finalizer транзакции не является функцией'; return $?; }
|
||||
fi
|
||||
set_runtime_paths || return $?
|
||||
for transaction_path in "$WG_CONF" "$LOCK_FILE" "$BACKUP_DIR" "$METADATA_DIR"; do
|
||||
path_components_safe "$transaction_path" || { die "$EXIT_VALIDATION" 'Путь транзакции содержит символическую ссылку'; return $?; }
|
||||
@@ -848,6 +887,10 @@ run_transaction_core() {
|
||||
export TRANSACTION_IN_CRITICAL=1
|
||||
if "$builder" "$stage"; then :; else status=$?; fi
|
||||
unset TRANSACTION_IN_CRITICAL
|
||||
if [[ -n ${TRANSACTION_PENDING_SIGNAL:-} ]]; then
|
||||
printf 'Ошибка: операция прервана сигналом %s\n' "$TRANSACTION_PENDING_SIGNAL" >&2
|
||||
status=$EXIT_OPERATION
|
||||
fi
|
||||
if ((status != 0)); then transaction_cleanup; exec {lock_fd}>&-; return "$status"; fi
|
||||
[[ -s "$stage" && -f "$stage" && ! -L "$stage" ]] || { die "$EXIT_VALIDATION" 'Builder не создал безопасный staged config'; status=$?; transaction_cleanup; exec {lock_fd}>&-; return "$status"; }
|
||||
create_transaction_temp strip.new || status=$?; new_stripped=$TRANSACTION_CREATED_TEMP
|
||||
@@ -873,7 +916,26 @@ run_transaction_core() {
|
||||
current_dump=$(wg show "$WG_INTERFACE" dump 2>/dev/null) || status=$EXIT_OPERATION
|
||||
[[ $status == 0 ]] && verify_expected_dump "$WG_CONF" "$current_dump" || status=$EXIT_OPERATION
|
||||
fi
|
||||
if (( status == 0 )) && [[ -n ${TRANSACTION_FINALIZER:-} ]]; then
|
||||
if "$TRANSACTION_FINALIZER"; then
|
||||
trap '' INT TERM
|
||||
if [[ -n ${TRANSACTION_PENDING_SIGNAL:-} ]]; then
|
||||
trap 'transaction_signal INT' INT
|
||||
trap 'transaction_signal TERM' TERM
|
||||
status=$EXIT_OPERATION
|
||||
else
|
||||
TRANSACTION_OWNED_PATHS=()
|
||||
fi
|
||||
else
|
||||
status=$EXIT_OPERATION
|
||||
fi
|
||||
fi
|
||||
if [[ -n ${TRANSACTION_PENDING_SIGNAL:-} ]]; then
|
||||
printf 'Ошибка: операция прервана сигналом %s\n' "$TRANSACTION_PENDING_SIGNAL" >&2
|
||||
status=$EXIT_OPERATION
|
||||
fi
|
||||
if ((status != 0)) && [[ -n ${backup_path:-} ]]; then
|
||||
TRANSACTION_ROLLBACK_ACTIVE=1
|
||||
if ! cp -- "$original_copy" "$stage" || ! chmod 600 -- "$stage" || { [[ ${TRANSACTION_ROLLBACK_REPLACE_FAIL:-0} == 1 ]] && transaction_test_hooks_enabled; } || ! mv -- "$stage" "$WG_CONF" || ! restore_file_metadata "$WG_CONF" "$source_mode" "$source_uid" "$source_gid"; then
|
||||
printf 'КРИТИЧНО: откат диска не выполнен\n' >&2; status=$EXIT_ROLLBACK
|
||||
elif ! run_syncconf "$WG_INTERFACE" "$old_stripped"; then
|
||||
@@ -882,8 +944,17 @@ run_transaction_core() {
|
||||
rollback_status=0
|
||||
prior_dump=$(wg show "$WG_INTERFACE" dump 2>/dev/null) || rollback_status=$EXIT_ROLLBACK
|
||||
if ((rollback_status == 0)) && ! verify_expected_dump "$WG_CONF" "$prior_dump"; then rollback_status=$EXIT_ROLLBACK; fi
|
||||
if ((rollback_status != 0)); then printf 'КРИТИЧНО: проверка отката не выполнена\n' >&2; status=$EXIT_ROLLBACK; else status=$EXIT_OPERATION; fi
|
||||
if ((rollback_status != 0)); then
|
||||
printf 'КРИТИЧНО: проверка отката не выполнена\n' >&2
|
||||
status=$EXIT_ROLLBACK
|
||||
elif (( TRANSACTION_ROLLBACK_INTERRUPTED == 1 )); then
|
||||
printf 'КРИТИЧНО: откат прерван сигналом %s\n' "$TRANSACTION_ROLLBACK_SIGNAL" >&2
|
||||
status=$EXIT_ROLLBACK
|
||||
else
|
||||
status=$EXIT_OPERATION
|
||||
fi
|
||||
fi
|
||||
TRANSACTION_ROLLBACK_ACTIVE=0
|
||||
fi
|
||||
if (( status == 0 )) && [[ -n ${backup_path:-} ]]; then
|
||||
if ! prune_backups; then
|
||||
@@ -922,10 +993,371 @@ run_transaction() {
|
||||
fi
|
||||
rm -f -- "$result_file"
|
||||
TRANSACTION_BACKUP_PATH=${result_backup:-}
|
||||
unset TRANSACTION_RESULT_FILE TRANSACTION_PRE_LOCK_FILE TRANSACTION_PRE_WG_CONF TRANSACTION_PRE_BACKUP_DIR TRANSACTION_PRE_METADATA_DIR TRANSACTION_PRE_INTERFACE
|
||||
unset TRANSACTION_RESULT_FILE TRANSACTION_PRE_LOCK_FILE TRANSACTION_PRE_WG_CONF TRANSACTION_PRE_BACKUP_DIR TRANSACTION_PRE_METADATA_DIR TRANSACTION_PRE_INTERFACE TRANSACTION_FINALIZER
|
||||
return "$result_status"
|
||||
}
|
||||
|
||||
validate_client_name() {
|
||||
local name=$1
|
||||
[[ -n $name && ${#name} -le 64 && $name =~ ^[A-Za-z0-9._-]+$ && $name != -* ]] || return 1
|
||||
is_control_free "$name"
|
||||
}
|
||||
|
||||
require_add_privilege() {
|
||||
if (( EUID == 0 )); then return 0; fi
|
||||
transaction_test_hooks_enabled || {
|
||||
die "$EXIT_PERMISSION" 'Для добавления клиента требуются права root'
|
||||
return $?
|
||||
}
|
||||
}
|
||||
|
||||
client_paths_available() {
|
||||
local name=$1 path
|
||||
for path in "${METADATA_DIR}/${name}.meta" "${CLIENT_DIR}/${name}.conf" "${CLIENT_DIR}/${name}.key" "${CLIENT_DIR}/${name}.pub" "${CLIENT_DIR}/${name}.psk"; do
|
||||
[[ ! -e $path && ! -L $path ]] || return 1
|
||||
done
|
||||
}
|
||||
|
||||
client_name_conflict() {
|
||||
local name=$1 key
|
||||
for key in "${!META_NAME[@]}"; do [[ ${META_NAME[$key]} == "$name" ]] && return 0; done
|
||||
for key in "${!DISK_MARKER[@]}"; do [[ ${DISK_MARKER[$key]} == "$name" || ${DISK_LEGACY[$key]} == "$name" ]] && return 0; done
|
||||
return 1
|
||||
}
|
||||
|
||||
allocate_client_ipv4() {
|
||||
local host candidate key allowed conflict
|
||||
for ((host = 10#$START_HOST; host <= 10#$END_HOST; host++)); do
|
||||
candidate="${VPN_SUBNET_PREFIX}.${host}/32"
|
||||
conflict=0
|
||||
for key in "${!DISK_ALLOWED[@]}"; do
|
||||
allowed=${DISK_ALLOWED[$key]}
|
||||
if allowed_sets_overlap "$candidate" "$allowed"; then conflict=1; break; fi
|
||||
done
|
||||
if (( conflict == 0 )); then
|
||||
for key in "${!META_IPV4[@]}"; do
|
||||
if allowed_sets_overlap "$candidate" "${META_IPV4[$key]}/32"; then conflict=1; break; fi
|
||||
done
|
||||
fi
|
||||
if (( conflict == 0 )); then printf '%s\n' "$candidate"; return 0; fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
allocate_client_ipv6() {
|
||||
local host=${1##*.} hex candidate key allowed
|
||||
[[ $USE_IPV6 == 1 ]] || return 0
|
||||
candidate=$(printf '%s%s/128' "$IPV6_PREFIX" "$(printf '%x' "$host")")
|
||||
for key in "${!DISK_ALLOWED[@]}"; do
|
||||
allowed=${DISK_ALLOWED[$key]}
|
||||
if allowed_sets_overlap "$candidate" "$allowed"; then return 1; fi
|
||||
done
|
||||
for key in "${!META_IPV6[@]}"; do
|
||||
[[ -n ${META_IPV6[$key]} ]] || continue
|
||||
if cidrs_overlap "$candidate" "${META_IPV6[$key]}/128"; then return 1; fi
|
||||
done
|
||||
printf '%s\n' "$candidate"
|
||||
}
|
||||
|
||||
client_allowed_ips() {
|
||||
case $1 in
|
||||
full-tunnel) printf '0.0.0.0/0,::/0' ;;
|
||||
split-tunnel) printf '%s.0/24' "$VPN_SUBNET_PREFIX" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
CAPTURED_KEY=''
|
||||
capture_single_key() {
|
||||
local mode=$1 input=$2 path line
|
||||
local -a lines=()
|
||||
shift 2
|
||||
CAPTURED_KEY=''
|
||||
path=$(mktemp "${WG_CONF%/*}/.wg-manager.key.XXXXXX") || return "$EXIT_OPERATION"
|
||||
chmod 600 -- "$path" || { rm -f -- "$path"; return "$EXIT_OPERATION"; }
|
||||
TRANSACTION_TEMP_PATHS+=("$path")
|
||||
if [[ $mode == stdin ]]; then
|
||||
if ! printf '%s\n' "$input" | "$@" >"$path"; then rm -f -- "$path"; return "$EXIT_OPERATION"; fi
|
||||
elif ! "$@" >"$path"; then
|
||||
rm -f -- "$path"
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
mapfile -t lines <"$path"
|
||||
rm -f -- "$path"
|
||||
((${#lines[@]} == 1)) || return "$EXIT_OPERATION"
|
||||
line=${lines[0]}
|
||||
is_wireguard_key "$line" || return "$EXIT_OPERATION"
|
||||
CAPTURED_KEY=$line
|
||||
}
|
||||
|
||||
read_server_public_key() {
|
||||
capture_single_key none '' wg show "$WG_INTERFACE" public-key || return "$EXIT_OPERATION"
|
||||
printf '%s\n' "$CAPTURED_KEY"
|
||||
}
|
||||
|
||||
generate_client_keys() {
|
||||
local private public psk status=0 xtrace=0
|
||||
[[ $- == *x* ]] && xtrace=1
|
||||
set +x
|
||||
if capture_single_key none '' wg genkey; then private=$CAPTURED_KEY; else status=$EXIT_OPERATION; fi
|
||||
if (( status == 0 )); then if capture_single_key stdin "$private" wg pubkey; then public=$CAPTURED_KEY; else status=$EXIT_OPERATION; fi; fi
|
||||
if (( status == 0 )); then if capture_single_key none '' wg genpsk; then psk=$CAPTURED_KEY; else status=$EXIT_OPERATION; fi; fi
|
||||
(( status == 0 )) || { (( xtrace == 1 )) && set -x; return "$status"; }
|
||||
ADD_PRIVATE_KEY=$private ADD_PUBLIC_KEY=$public ADD_PSK=$psk
|
||||
(( xtrace == 1 )) && set -x
|
||||
return 0
|
||||
}
|
||||
|
||||
create_add_stage_file() {
|
||||
local directory=$1 prefix=$2 path kind
|
||||
kind=${prefix##*.}
|
||||
if [[ ${TRANSACTION_ADD_STAGE_FAIL:-} == "$prefix" || ${TRANSACTION_ADD_STAGE_FAIL:-} == "$kind" ]] && transaction_test_hooks_enabled; then return "$EXIT_OPERATION"; fi
|
||||
path=$(mktemp "$directory/.wg-manager.add.${prefix}.XXXXXX") || return "$EXIT_OPERATION"
|
||||
if [[ ! -f "$path" || -L "$path" ]] || ! chmod 600 -- "$path"; then
|
||||
rm -f -- "$path"
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
TRANSACTION_TEMP_PATHS+=("$path")
|
||||
printf '%s\n' "$path"
|
||||
}
|
||||
|
||||
add_client_builder() {
|
||||
local stage=$1 name=$ADD_NAME mode=$ADD_MODE endpoint=$ADD_ENDPOINT ipv4 ipv6 server_public allowed_client allowed_server
|
||||
local meta_file config_file key_file pub_file psk_file
|
||||
set +x
|
||||
[[ ${TRANSACTION_IN_CRITICAL:-0} == 1 ]] || return "$EXIT_OPERATION"
|
||||
validate_client_name "$name" || return "$EXIT_VALIDATION"
|
||||
parse_wireguard_config "$WG_CONF" || return $?
|
||||
parse_metadata || return $?
|
||||
if client_name_conflict "$name"; then
|
||||
add_failure "$EXIT_VALIDATION" 'имя клиента уже используется'
|
||||
return $?
|
||||
fi
|
||||
if ! client_paths_available "$name"; then
|
||||
add_failure "$EXIT_VALIDATION" 'целевой файл клиента уже существует или небезопасен'
|
||||
return $?
|
||||
fi
|
||||
if ! ipv4=$(allocate_client_ipv4); then
|
||||
add_failure "$EXIT_VALIDATION" 'диапазон IPv4-адресов клиентов исчерпан'
|
||||
return $?
|
||||
fi
|
||||
if ! ipv6=$(allocate_client_ipv6 "${ipv4%/*}"); then
|
||||
add_failure "$EXIT_VALIDATION" 'адрес IPv6 клиента конфликтует или недоступен'
|
||||
return $?
|
||||
fi
|
||||
if ! allowed_client=$(client_allowed_ips "$mode"); then
|
||||
add_failure "$EXIT_VALIDATION" 'некорректный режим адресов клиента'
|
||||
return $?
|
||||
fi
|
||||
if ! server_public=$(read_server_public_key); then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось получить открытый ключ сервера'
|
||||
return $?
|
||||
fi
|
||||
if ! generate_client_keys; then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось сгенерировать ключи клиента'
|
||||
return $?
|
||||
fi
|
||||
if [[ -n ${DISK_ALLOWED[$ADD_PUBLIC_KEY]+x} ]]; then
|
||||
add_failure "$EXIT_VALIDATION" 'сгенерированный открытый ключ уже используется'
|
||||
return $?
|
||||
fi
|
||||
if [[ -n ${META_NAME[$ADD_PUBLIC_KEY]+x} ]]; then
|
||||
add_failure "$EXIT_VALIDATION" 'сгенерированный открытый ключ уже есть в метаданных'
|
||||
return $?
|
||||
fi
|
||||
allowed_server="$ipv4"; [[ -n $ipv6 ]] && allowed_server+=",$ipv6"
|
||||
meta_file=${ADD_META_STAGE:?metadata stage is not prepared}
|
||||
config_file=${ADD_CONFIG_STAGE:?config stage is not prepared}
|
||||
key_file=${ADD_KEY_STAGE:?key stage is not prepared}
|
||||
pub_file=${ADD_PUB_STAGE:?public key stage is not prepared}
|
||||
psk_file=${ADD_PSK_STAGE:?psk stage is not prepared}
|
||||
TRANSACTION_TEMP_PATHS+=("$meta_file" "$config_file" "$key_file" "$pub_file" "$psk_file")
|
||||
{
|
||||
cat -- "$WG_CONF"
|
||||
printf '\n# wg-manager-client: %s\n[Peer]\nPublicKey = %s\nPresharedKey = %s\nAllowedIPs = %s\n' "$name" "$ADD_PUBLIC_KEY" "$ADD_PSK" "$allowed_server"
|
||||
} >"$stage"
|
||||
printf '%s\n' "$ADD_PRIVATE_KEY" >"$key_file"
|
||||
printf '%s\n' "$ADD_PUBLIC_KEY" >"$pub_file"
|
||||
printf '%s\n' "$ADD_PSK" >"$psk_file"
|
||||
{
|
||||
printf '[Interface]\nPrivateKey = %s\nAddress = %s' "$ADD_PRIVATE_KEY" "$allowed_server"
|
||||
printf '\nDNS = %s\n\n[Peer]\nPublicKey = %s\nPresharedKey = %s\nEndpoint = %s:%s\nAllowedIPs = %s\nPersistentKeepalive = 25\n' "$CLIENT_DNS" "$server_public" "$ADD_PSK" "$endpoint" "$SERVER_PORT" "$allowed_client"
|
||||
} >"$config_file"
|
||||
printf 'NAME=%s\nPUBLIC_KEY=%s\nIPV4=%s\n' "$name" "$ADD_PUBLIC_KEY" "${ipv4%/*}" >"$meta_file"
|
||||
[[ -n $ipv6 ]] && printf 'IPV6=%s\n' "${ipv6%/*}" >>"$meta_file"
|
||||
printf 'CREATED_AT=%s\nCONFIG_PATH=%s\nCLIENT_MODE=%s\nMANAGED=1\n' "$(current_epoch)" "${CLIENT_DIR}/${name}.conf" "$mode" >>"$meta_file"
|
||||
chmod 600 -- "$meta_file" "$config_file" "$key_file" "$pub_file" "$psk_file"
|
||||
}
|
||||
|
||||
publish_add_artifacts() {
|
||||
local name=${ADD_NAME:?add name is not set} stage_path final_path published=()
|
||||
local -a stages=("$ADD_META_STAGE" "$ADD_CONFIG_STAGE" "$ADD_KEY_STAGE" "$ADD_PUB_STAGE" "$ADD_PSK_STAGE")
|
||||
local -a finals=("${METADATA_DIR}/${name}.meta" "${CLIENT_DIR}/${name}.conf" "${CLIENT_DIR}/${name}.key" "${CLIENT_DIR}/${name}.pub" "${CLIENT_DIR}/${name}.psk")
|
||||
local index
|
||||
if [[ ${ADD_PUBLISH_FAIL:-0} == 1 ]] && transaction_test_hooks_enabled; then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось опубликовать файлы клиента'
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
for index in "${!stages[@]}"; do
|
||||
stage_path=${stages[$index]}; final_path=${finals[$index]}
|
||||
if (( index == 0 )) && [[ ${ADD_PUBLISH_RACE:-0} == 1 ]] && transaction_test_hooks_enabled; then
|
||||
printf 'administrator collision\n' >"$final_path"
|
||||
chmod 600 -- "$final_path"
|
||||
fi
|
||||
if [[ ! -f "$stage_path" || -L "$stage_path" || -e "$final_path" || -L "$final_path" ]]; then
|
||||
add_failure "$EXIT_OPERATION" 'конфликт целевого файла клиента при публикации'
|
||||
for stage_path in "${stages[@]}" "${published[@]}"; do rm -f -- "$stage_path"; done
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! ln -- "$stage_path" "$final_path"; then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось опубликовать файл клиента'
|
||||
for stage_path in "${stages[@]}" "${published[@]}"; do rm -f -- "$stage_path"; done
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
TRANSACTION_OWNED_PATHS+=("$final_path")
|
||||
if ! rm -f -- "$stage_path"; then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось завершить публикацию файла клиента'
|
||||
rm -f -- "$final_path"
|
||||
for stage_path in "${stages[@]}" "${published[@]}"; do rm -f -- "$stage_path"; done
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
published+=("$final_path")
|
||||
if (( index == 0 || index == 2 || index == 4 )) && [[ -n ${ADD_PUBLISH_READY_FIFO:-} ]] && [[ -z ${ADD_PUBLISH_READY_INDEX:-} || ${ADD_PUBLISH_READY_INDEX} == "$index" ]]; then
|
||||
if ! transaction_test_hooks_enabled; then
|
||||
add_failure "$EXIT_OPERATION" "некорректная синхронизация публикации"
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! [[ ${ADD_PUBLISH_READY_FIFO} == "${TEST_ROOT}"/* && ! -L ${ADD_PUBLISH_READY_FIFO} && -p ${ADD_PUBLISH_READY_FIFO} && ${ADD_PUBLISH_RELEASE_FIFO:-} == "${TEST_ROOT}"/* && ! -L ${ADD_PUBLISH_RELEASE_FIFO} && -p ${ADD_PUBLISH_RELEASE_FIFO} ]]; then
|
||||
add_failure "$EXIT_OPERATION" "некорректная синхронизация публикации"
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if [[ -n ${ADD_PUBLISH_PID_FILE:-} ]]; then
|
||||
if ! [[ ${ADD_PUBLISH_PID_FILE} == "${TEST_ROOT}"/* && ! -L ${ADD_PUBLISH_PID_FILE} && ( ! -e ${ADD_PUBLISH_PID_FILE} || -f ${ADD_PUBLISH_PID_FILE} ) ]]; then
|
||||
add_failure "$EXIT_OPERATION" "некорректный файл синхронизации публикации"
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! printf '%s\n' "$BASHPID" >"$ADD_PUBLISH_PID_FILE"; then
|
||||
add_failure "$EXIT_OPERATION" "не удалось подготовить синхронизацию публикации"
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
fi
|
||||
if ! printf 'ready\n' >"$ADD_PUBLISH_READY_FIFO"; then
|
||||
add_failure "$EXIT_OPERATION" "не удалось подготовить синхронизацию публикации"
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! IFS= read -r _ <"$ADD_PUBLISH_RELEASE_FIFO"; then
|
||||
add_failure "$EXIT_OPERATION" "синхронизация публикации прервана"
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
clear_add_transaction_state() {
|
||||
local path
|
||||
for path in "$ADD_META_STAGE" "$ADD_CONFIG_STAGE" "$ADD_KEY_STAGE" "$ADD_PUB_STAGE" "$ADD_PSK_STAGE"; do
|
||||
[[ -n ${path:-} ]] && rm -f -- "$path"
|
||||
done
|
||||
unset ADD_NAME ADD_MODE ADD_ENDPOINT ADD_PRIVATE_KEY ADD_PUBLIC_KEY ADD_PSK ADD_META_STAGE ADD_CONFIG_STAGE ADD_KEY_STAGE ADD_PUB_STAGE ADD_PSK_STAGE ADD_PUBLISH_READY_FIFO ADD_PUBLISH_RELEASE_FIFO ADD_PUBLISH_PID_FILE ADD_PUBLISH_READY_INDEX
|
||||
TRANSACTION_FINALIZER=''
|
||||
}
|
||||
|
||||
add_client_impl() {
|
||||
local name mode endpoint arg mode_seen=0 endpoint_seen=0
|
||||
(($# >= 1)) || { die "$EXIT_USAGE" 'Ожидается имя клиента'; return $?; }
|
||||
name=$1; shift
|
||||
load_config || return $?
|
||||
mode=$DEFAULT_CLIENT_MODE; endpoint=$SERVER_ENDPOINT
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
--mode)
|
||||
(( mode_seen == 0 )) || { die "$EXIT_USAGE" 'Флаг --mode указан повторно'; return $?; }
|
||||
(($# >= 2)) || { die "$EXIT_USAGE" 'Для --mode требуется значение'; return $?; }
|
||||
mode=$2; mode_seen=1; shift 2 ;;
|
||||
--endpoint)
|
||||
(( endpoint_seen == 0 )) || { die "$EXIT_USAGE" 'Флаг --endpoint указан повторно'; return $?; }
|
||||
(($# >= 2)) || { die "$EXIT_USAGE" 'Для --endpoint требуется значение'; return $?; }
|
||||
endpoint=$2; endpoint_seen=1; shift 2 ;;
|
||||
*) die "$EXIT_USAGE" 'Неизвестный или лишний аргумент add'; return $? ;;
|
||||
esac
|
||||
done
|
||||
validate_client_name "$name" || { die "$EXIT_VALIDATION" 'Некорректное имя клиента'; return $?; }
|
||||
[[ $mode == full-tunnel || $mode == split-tunnel ]] || { die "$EXIT_VALIDATION" 'Некорректный режим клиента'; return $?; }
|
||||
validate_endpoint "$endpoint" || { die "$EXIT_VALIDATION" 'Некорректный endpoint клиента'; return $?; }
|
||||
require_add_privilege || return $?
|
||||
ADD_META_STAGE='' ADD_CONFIG_STAGE='' ADD_KEY_STAGE='' ADD_PUB_STAGE='' ADD_PSK_STAGE=''
|
||||
if [[ ${TRANSACTION_ADD_DIRECTORY_FAIL:-} == mkdir ]] && transaction_test_hooks_enabled; then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось подготовить каталоги клиента'
|
||||
clear_add_transaction_state
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! mkdir -p -- "$METADATA_DIR" "$CLIENT_DIR"; then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось создать каталоги клиента'
|
||||
clear_add_transaction_state
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if [[ ${TRANSACTION_ADD_DIRECTORY_FAIL:-} == chmod ]] && transaction_test_hooks_enabled; then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось защитить каталоги клиента'
|
||||
clear_add_transaction_state
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! chmod 700 -- "$METADATA_DIR" "$CLIENT_DIR"; then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось защитить каталоги клиента'
|
||||
clear_add_transaction_state
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! ADD_META_STAGE=$(create_add_stage_file "$METADATA_DIR" "${name}.meta"); then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось подготовить метаданные клиента'
|
||||
clear_add_transaction_state
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! ADD_CONFIG_STAGE=$(create_add_stage_file "$CLIENT_DIR" "${name}.conf"); then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось подготовить конфигурацию клиента'
|
||||
clear_add_transaction_state
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! ADD_KEY_STAGE=$(create_add_stage_file "$CLIENT_DIR" "${name}.key"); then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось подготовить закрытый ключ клиента'
|
||||
clear_add_transaction_state
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! ADD_PUB_STAGE=$(create_add_stage_file "$CLIENT_DIR" "${name}.pub"); then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось подготовить открытый ключ клиента'
|
||||
clear_add_transaction_state
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! ADD_PSK_STAGE=$(create_add_stage_file "$CLIENT_DIR" "${name}.psk"); then
|
||||
add_failure "$EXIT_OPERATION" 'не удалось подготовить дополнительный ключ клиента'
|
||||
clear_add_transaction_state
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
ADD_NAME=$name ADD_MODE=$mode ADD_ENDPOINT=$endpoint
|
||||
TRANSACTION_FINALIZER=publish_add_artifacts
|
||||
local status
|
||||
if run_transaction add "$name" add_client_builder; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
if (( status != 0 )); then
|
||||
clear_add_transaction_state
|
||||
return "$status"
|
||||
fi
|
||||
status=0
|
||||
clear_add_transaction_state
|
||||
return "$status"
|
||||
}
|
||||
|
||||
add_client() {
|
||||
local caller_xtrace=0 status
|
||||
[[ $- == *x* ]] && caller_xtrace=1
|
||||
set +x
|
||||
if add_client_impl "$@"; then status=0; else status=$?; fi
|
||||
(( caller_xtrace == 1 )) && set -x
|
||||
return "$status"
|
||||
}
|
||||
|
||||
print_status() {
|
||||
local managed=0 unmanaged=0 active=0 index
|
||||
for index in "${!CLIENT_KEYS[@]}"; do
|
||||
@@ -964,8 +1396,23 @@ print_version() { printf 'WireGuard Manager %s\n' "$WG_MANAGER_VERSION"; }
|
||||
run_placeholder() { load_config || return $?; die "$EXIT_OPERATION" "Команда «$1» пока не реализована"; }
|
||||
|
||||
main() {
|
||||
local status
|
||||
if (( $# == 0 )); then
|
||||
if die "$EXIT_USAGE" 'Ожидается команда'; then status=0; else status=$?; fi
|
||||
print_usage >&2
|
||||
return "$status"
|
||||
fi
|
||||
if [[ $1 == add ]]; then
|
||||
shift
|
||||
if (( $# == 0 )); then
|
||||
if die "$EXIT_USAGE" 'Ожидается имя клиента'; then status=0; else status=$?; fi
|
||||
print_usage >&2
|
||||
return "$status"
|
||||
fi
|
||||
add_client "$@"
|
||||
return $?
|
||||
fi
|
||||
if (( $# != 1 )); then
|
||||
local status
|
||||
if die "$EXIT_USAGE" 'Ожидается одна команда'; then status=0; else status=$?; fi
|
||||
print_usage >&2
|
||||
return "$status"
|
||||
@@ -975,9 +1422,8 @@ main() {
|
||||
version) print_version ;;
|
||||
status) load_client_model || return $?; print_status ;;
|
||||
list) load_client_model || return $?; print_list ;;
|
||||
add|show|config|qr|remove|diagnose|logs|backups|restore|tui) run_placeholder "$1" ;;
|
||||
show|config|qr|remove|diagnose|logs|backups|restore|tui) run_placeholder "$1" ;;
|
||||
*)
|
||||
local status
|
||||
if die "$EXIT_USAGE" "неизвестная команда: $1"; then status=0; else status=$?; fi
|
||||
print_usage >&2
|
||||
return "$status"
|
||||
|
||||
Reference in New Issue
Block a user