feat: профиль freeturn vpn со вшитым wg и фикс ui_spin

- фикс вызова функций в ui_spin при наличии gum (устранена ошибка _dl: command not found)

- поддержка поля wg в freeturn:// ссылках для автонастройки VPN в мобильном приложении

- генерация отдельного профиля и выбор QR-кода со вшитым VPN в install.sh
This commit is contained in:
samosvalishe
2026-09-07 09:47:58 +03:00
parent 2544be2ed6
commit 9da2c8ebd2
4 changed files with 51 additions and 12 deletions
+4
View File
@@ -103,6 +103,10 @@ func (r *raw) applyURI(u *uri.Config) {
if u.ManualCaptcha {
r.ManualCaptcha = true
}
if u.WGConf != "" {
r.TunnelMode = "awg"
r.TunnelConfig = u.WGConf
}
}
func assemble(r raw) (*Client, error) {
+4
View File
@@ -29,6 +29,7 @@ type Config struct {
ManualCaptcha bool
KCP *KCP
Comment string
WGConf string
}
// wire - JSON-схема freeturn:// ссылок.
@@ -49,6 +50,7 @@ type wire struct {
ManualCaptcha bool `json:"mcap,omitempty"`
KCP *KCP `json:"kcp,omitempty"`
Name string `json:"name,omitempty"`
WGConf string `json:"wg,omitempty"`
}
// Parse разбирает строку freeturn://<base64url(json)>.
@@ -97,6 +99,7 @@ func Parse(s string) (*Config, error) {
ManualCaptcha: w.ManualCaptcha,
KCP: w.KCP,
Comment: w.Name,
WGConf: w.WGConf,
}, nil
}
@@ -117,6 +120,7 @@ func (c *Config) String() string {
ManualCaptcha: c.ManualCaptcha,
KCP: c.KCP,
Name: c.Comment,
WGConf: c.WGConf,
}
if c.ObfProfile != "" && c.ObfProfile != "none" {
w.Obf = c.ObfProfile
+10
View File
@@ -48,6 +48,16 @@ func TestRoundTrip(t *testing.T) {
Comment: "Сервер РФ",
},
},
{
name: "with wg conf",
cfg: &Config{
Version: currentVersion,
Provider: "vk",
Peer: "1.2.3.4:56000",
Comment: "VPN Server",
WGConf: "[Interface]\nPrivateKey = test\n[Peer]\nPublicKey = test\n",
},
},
}
for _, tt := range tests {
+33 -12
View File
@@ -325,8 +325,11 @@ ui_spin() {
local title="$1"; shift
local rc=0 log; log="$(mktemp)"
if [ "$HAS_GUM" = 1 ]; then
if gum spin --spinner dot --spinner.foreground "$MD_PRIMARY" --title "$title" \
-- bash -c '"$@" >"$0" 2>&1' "$log" "$@"; then rc=0; else rc=$?; fi
( "$@" >"$log" 2>&1 ) &
local pid=$!
gum spin --spinner dot --spinner.foreground "$MD_PRIMARY" --title "$title" \
-- bash -c 'while kill -0 "$1" 2>/dev/null; do sleep 0.1; done' _ "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null && rc=0 || rc=$?
if [ "$rc" -eq 0 ]; then log_success "$title"
else
log_error "$title - ошибка (код $rc)"
@@ -338,7 +341,7 @@ ui_spin() {
while kill -0 "$pid" 2>/dev/null; do
i=$(((i + 1) % 4)); printf "\r${C_CYAN}[*]${C_NC} %s %s" "$title" "${ch:$i:1}"; sleep 0.2
done
wait "$pid" && rc=0 || rc=$?
wait "$pid" 2>/dev/null && rc=0 || rc=$?
if [ "$rc" -eq 0 ]; then printf "\r${C_GREEN}[+]${C_NC} %s\033[K\n" "$title"
else printf "\r${C_RED}[x]${C_NC} %s\033[K\n" "$title"; tail -n 40 "$log" >&2; fi
else
@@ -1198,7 +1201,7 @@ wg_reconcile() {
_pub_fs() { printf '%s' "$1" | tr '/+' '_-' | tr -d '='; }
generate_freeturn_uri() {
local peer="$1" mode="$2" obf="$3" key="$4" cid="${5:-}" name="${6:-}"
local peer="$1" mode="$2" obf="$3" key="$4" cid="${5:-}" name="${6:-}" wg_conf="${7:-}"
local json b64
json="{\"v\":1,\"provider\":\"$(esc "${PROVIDER:-vk}")\",\"peer\":\"$(esc "$peer")\""
if [ -n "$mode" ] && [ "$mode" != "udp" ]; then
@@ -1215,6 +1218,9 @@ generate_freeturn_uri() {
if [ -n "$name" ]; then
json="$json,\"name\":\"$(esc "$name")\""
fi
if [ -n "$wg_conf" ]; then
json="$json,\"wg\":\"$(esc "$wg_conf")\""
fi
json="$json}"
b64=$(printf '%s' "$json" | openssl base64 -A 2>/dev/null || printf '%s' "$json" | base64 | tr -d '\r\n')
b64=$(printf '%s' "$b64" | tr '+/' '-_' | tr -d '=')
@@ -2203,17 +2209,29 @@ EOF
[ -n "$cid" ] && printf '%s\n' "$cid" > "$SHARE_DIR/$(_pub_fs "$cli_pub").cid" 2>/dev/null || true
fi
local ft_uri=""
local ft_uri="" ft_vpn_uri=""
if [ "$INSTALL_FREETURN" = "1" ]; then
ft_uri="$(generate_freeturn_uri "${ext_ip}:${LISTEN_PORT}" "${PROXY_MODE}" "${OBF_PROFILE}" "${OBF_KEY}" "${cid}" "${cname}")"
ft_file="${CLIENTS_DIR}/${cname}-freeturn.txt"
echo "$ft_uri" > "$ft_file"; chmod 0600 "$ft_file"
if [ -f "$relay_conf" ]; then
ft_vpn_uri="$(generate_freeturn_uri "${ext_ip}:${LISTEN_PORT}" "${PROXY_MODE}" "${OBF_PROFILE}" "${OBF_KEY}" "${cid}" "${cname}" "$(<"$relay_conf")")"
ft_vpn_file="${CLIENTS_DIR}/${cname}-freeturn-vpn.txt"
echo "$ft_vpn_uri" > "$ft_vpn_file"; chmod 0600 "$ft_vpn_file"
fi
fi
echo "${cname}|${client_ip}|${cid}|$(date '+%Y-%m-%d %H:%M')" >> "$CLIENTS_META"
log_success "Клиент '${cname}' добавлен!"
if [ -n "$ft_uri" ]; then
if [ -n "$ft_vpn_uri" ]; then
render_qr_text "$ft_vpn_uri" "QR-код для FreeTurn App (со вшитым VPN - ${cname}):"
echo
log_info "Ссылка FreeTurn VPN (All-in-One): $ft_vpn_uri"
log_info "Ссылка FreeTurn Proxy (только релей): $ft_uri"
[ -n "$cid" ] && log_info "Client ID: $cid"
elif [ -n "$ft_uri" ]; then
render_qr_text "$ft_uri" "QR-код для приложения FreeTurn (${cname}):"
echo
log_info "Ссылка FreeTurn: $ft_uri"
@@ -2221,6 +2239,7 @@ EOF
fi
if [ -f "$direct_conf" ]; then
echo
render_qr_file "$direct_conf" "QR-код для AmneziaWG Direct (${cname}):"
echo
log_info "Конфиг AmneziaWG Direct: $direct_conf"
@@ -2253,9 +2272,10 @@ client_qr() {
if [ -z "$mode" ]; then
local opts=()
[ -f "${CLIENTS_DIR}/${cname}-freeturn.txt" ] && opts+=(freeturn "Приложение FreeTurn (freeturn://)")
[ -f "${CLIENTS_DIR}/${cname}-freeturn-vpn.txt" ] && opts+=(freeturn_vpn "FreeTurn App (со вшитым VPN)")
[ -f "${CLIENTS_DIR}/${cname}-freeturn.txt" ] && opts+=(freeturn "FreeTurn App (прокси-режим)")
[ -f "${CLIENTS_DIR}/${cname}-direct.conf" ] && opts+=(direct "AmneziaWG Direct (AWG 3.1)")
[ -f "${CLIENTS_DIR}/${cname}-relay.conf" ] && opts+=(relay "FreeTurn Relay (WireGuard)")
[ -f "${CLIENTS_DIR}/${cname}-relay.conf" ] && opts+=(relay "FreeTurn Relay (WireGuard/AWG)")
if [ "${#opts[@]}" -gt 2 ]; then
ui_menu mode "Формат QR-кода:" "${opts[0]}" "${opts[@]}"
elif [ "${#opts[@]}" -eq 2 ]; then
@@ -2266,9 +2286,10 @@ client_qr() {
fi
case "$mode" in
direct) render_qr_file "${CLIENTS_DIR}/${cname}-direct.conf" "QR AmneziaWG Direct (${cname}):" ;;
relay) render_qr_file "${CLIENTS_DIR}/${cname}-relay.conf" "QR FreeTurn Relay (${cname}):" ;;
freeturn) [ -f "${CLIENTS_DIR}/${cname}-freeturn.txt" ] && render_qr_text "$(<"${CLIENTS_DIR}/${cname}-freeturn.txt")" "QR FreeTurn App (${cname}):" ;;
freeturn_vpn) [ -f "${CLIENTS_DIR}/${cname}-freeturn-vpn.txt" ] && render_qr_text "$(<"${CLIENTS_DIR}/${cname}-freeturn-vpn.txt")" "QR FreeTurn App (со вшитым VPN - ${cname}):" ;;
freeturn) [ -f "${CLIENTS_DIR}/${cname}-freeturn.txt" ] && render_qr_text "$(<"${CLIENTS_DIR}/${cname}-freeturn.txt")" "QR FreeTurn App (прокси - ${cname}):" ;;
direct) render_qr_file "${CLIENTS_DIR}/${cname}-direct.conf" "QR AmneziaWG Direct (${cname}):" ;;
relay) render_qr_file "${CLIENTS_DIR}/${cname}-relay.conf" "QR FreeTurn Relay (${cname}):" ;;
esac
}
@@ -2296,7 +2317,7 @@ client_remove() {
rm -f "$tmp"
fi
rm -f "${CLIENTS_DIR}/${cname}-direct.conf" "${CLIENTS_DIR}/${cname}-relay.conf" "${CLIENTS_DIR}/${cname}-freeturn.txt"
rm -f "${CLIENTS_DIR}/${cname}-direct.conf" "${CLIENTS_DIR}/${cname}-relay.conf" "${CLIENTS_DIR}/${cname}-freeturn.txt" "${CLIENTS_DIR}/${cname}-freeturn-vpn.txt"
[ -f "$CLIENTS_META" ] && sed -i "/^${cname}|/d" "$CLIENTS_META"
log_success "Клиент '${cname}' удалён."
}