xrayvpn 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xrayvpn/__init__.py +3 -0
- xrayvpn/__main__.py +6 -0
- xrayvpn/cli/__init__.py +0 -0
- xrayvpn/cli/entry.py +15 -0
- xrayvpn/cli/l10n_typer.py +281 -0
- xrayvpn/cli/main.py +1036 -0
- xrayvpn/cli/prompts.py +53 -0
- xrayvpn/cli/repl.py +408 -0
- xrayvpn/cli/service.py +444 -0
- xrayvpn/cli/terminal_host.py +282 -0
- xrayvpn/cli/theme.py +297 -0
- xrayvpn/core/__init__.py +0 -0
- xrayvpn/core/config.py +60 -0
- xrayvpn/core/conn.py +186 -0
- xrayvpn/core/deploy_lock.py +80 -0
- xrayvpn/core/execution/__init__.py +0 -0
- xrayvpn/core/execution/base.py +46 -0
- xrayvpn/core/execution/local.py +273 -0
- xrayvpn/core/execution/remote.py +329 -0
- xrayvpn/core/inventory.py +145 -0
- xrayvpn/core/manifest.py +57 -0
- xrayvpn/core/runtime_paths.py +257 -0
- xrayvpn/core/service_actions.py +209 -0
- xrayvpn/core/transport/__init__.py +0 -0
- xrayvpn/core/transport/remote.py +145 -0
- xrayvpn/core/update_check.py +147 -0
- xrayvpn/core/wsl.py +92 -0
- xrayvpn/i18n.py +55 -0
- xrayvpn/payload/config/README.en.md +56 -0
- xrayvpn/payload/config/README.md +56 -0
- xrayvpn/payload/config/settings.yml +79 -0
- xrayvpn/payload/deploy.yml +15 -0
- xrayvpn/payload/inventory.yml.example +8 -0
- xrayvpn/payload/roles/xray_vpn/handlers/main.yml +23 -0
- xrayvpn/payload/roles/xray_vpn/meta/main.yml +19 -0
- xrayvpn/payload/roles/xray_vpn/tasks/keys.yml +421 -0
- xrayvpn/payload/roles/xray_vpn/tasks/logs.yml +19 -0
- xrayvpn/payload/roles/xray_vpn/tasks/main.yml +169 -0
- xrayvpn/payload/roles/xray_vpn/tasks/obs.yml +60 -0
- xrayvpn/payload/roles/xray_vpn/tasks/runtime_docker.yml +33 -0
- xrayvpn/payload/roles/xray_vpn/tasks/runtime_native.yml +69 -0
- xrayvpn/payload/roles/xray_vpn/tasks/runtime_setup.yml +31 -0
- xrayvpn/payload/roles/xray_vpn/tasks/smoke.yml +98 -0
- xrayvpn/payload/roles/xray_vpn/tasks/warp.yml +107 -0
- xrayvpn/payload/roles/xray_vpn/tasks/watchdog.yml +66 -0
- xrayvpn/payload/roles/xray_vpn/templates/clash_meta.yaml.j2 +119 -0
- xrayvpn/payload/roles/xray_vpn/templates/client_config.json.j2 +46 -0
- xrayvpn/payload/roles/xray_vpn/templates/journald-60-xray-ansible.conf.j2 +6 -0
- xrayvpn/payload/roles/xray_vpn/templates/xray-obs-snapshot.service.j2 +6 -0
- xrayvpn/payload/roles/xray_vpn/templates/xray-obs-snapshot.sh.j2 +28 -0
- xrayvpn/payload/roles/xray_vpn/templates/xray-obs-snapshot.timer.j2 +10 -0
- xrayvpn/payload/roles/xray_vpn/templates/xray-watchdog.service.j2 +6 -0
- xrayvpn/payload/roles/xray_vpn/templates/xray-watchdog.sh.j2 +111 -0
- xrayvpn/payload/roles/xray_vpn/templates/xray-watchdog.timer.j2 +10 -0
- xrayvpn/payload/roles/xray_vpn/templates/xray.service.docker.j2 +26 -0
- xrayvpn/payload/roles/xray_vpn/templates/xray.service.native.j2 +26 -0
- xrayvpn/payload/roles/xray_vpn/templates/xray_config.json.j2 +98 -0
- xrayvpn/text/__init__.py +623 -0
- xrayvpn-0.4.1.dist-info/METADATA +174 -0
- xrayvpn-0.4.1.dist-info/RECORD +62 -0
- xrayvpn-0.4.1.dist-info/WHEEL +4 -0
- xrayvpn-0.4.1.dist-info/entry_points.txt +2 -0
xrayvpn/__init__.py
ADDED
xrayvpn/__main__.py
ADDED
xrayvpn/cli/__init__.py
ADDED
|
File without changes
|
xrayvpn/cli/entry.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Process entry: the conhost gate runs before any heavy CLI import."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from xrayvpn.cli import terminal_host
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
terminal_host.relaunch_if_delegated()
|
|
12
|
+
sys.argv[:] = [sys.argv[0], *terminal_host.strip_wt_flag(sys.argv[1:])]
|
|
13
|
+
from xrayvpn.cli.main import run
|
|
14
|
+
|
|
15
|
+
run()
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Russian translations and palette theming for Typer/Click built-ins (typer 0.27.x).
|
|
2
|
+
|
|
3
|
+
Typer builds `--help` at import time and hard-codes its built-ins in
|
|
4
|
+
English. `apply_ru()` patches the finite set of user-visible strings;
|
|
5
|
+
`revert_ru()` restores the saved originals (idempotent pair, runtime-
|
|
6
|
+
switchable for the REPL session — already-built help options keep the
|
|
7
|
+
language chosen at process start). `apply_palette()` re-styles the rich
|
|
8
|
+
help panels onto the project color tokens (cli/theme.py), normalizes the EN
|
|
9
|
+
help-suggestion string, and is applied unconditionally at startup before
|
|
10
|
+
`apply_ru()`; it is never reverted. The dependency is pinned
|
|
11
|
+
to typer <0.28: the patches touch private internals, and the pin plus
|
|
12
|
+
tests/test_i18n.py and tests/test_cli_palette.py bound the drift.
|
|
13
|
+
|
|
14
|
+
Reachability on the current CLI surface — live and test-guarded: panel
|
|
15
|
+
titles, RICH_HELP, DEFAULT_STRING, the Usage prefix, the help-option text,
|
|
16
|
+
NoSuchOption/BadParameter/UsageError messages, translate_message maps, and
|
|
17
|
+
the plain-text show() fallbacks (rich normally routes errors around them;
|
|
18
|
+
unit-tested by direct call). Defensive forward-compat, unreachable today:
|
|
19
|
+
ARGUMENTS_PANEL_TITLE and REQUIRED_LONG_STRING (no positional/required
|
|
20
|
+
params), MissingParameter and FileError messages (same reason), and
|
|
21
|
+
_TYPE_NAMES entries beyond int/integer/UUID.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import re
|
|
27
|
+
|
|
28
|
+
from xrayvpn.cli import theme
|
|
29
|
+
|
|
30
|
+
_BACKUP: list[tuple[object, str, object]] = []
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _rich_style(token: str, *, bold: bool = False) -> str:
|
|
34
|
+
hex_value = theme.TOKENS[token]
|
|
35
|
+
return f"bold {hex_value}" if bold else hex_value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
PALETTE_OVERRIDES: dict[str, str] = {
|
|
39
|
+
"STYLE_USAGE": _rich_style("accent", bold=True),
|
|
40
|
+
"STYLE_USAGE_COMMAND": _rich_style("accent", bold=True),
|
|
41
|
+
"STYLE_OPTION": _rich_style("accent", bold=True),
|
|
42
|
+
"STYLE_SWITCH": _rich_style("accent", bold=True),
|
|
43
|
+
"STYLE_COMMANDS_TABLE_FIRST_COLUMN": _rich_style("accent", bold=True),
|
|
44
|
+
"STYLE_NEGATIVE_OPTION": _rich_style("ok", bold=True),
|
|
45
|
+
"STYLE_NEGATIVE_SWITCH": _rich_style("ok", bold=True),
|
|
46
|
+
"STYLE_TYPES": _rich_style("muted"),
|
|
47
|
+
"STYLE_OPTION_ENVVAR": _rich_style("muted"),
|
|
48
|
+
"STYLE_OPTION_DEFAULT": _rich_style("muted"),
|
|
49
|
+
"STYLE_OPTIONS_PANEL_BORDER": _rich_style("muted"),
|
|
50
|
+
"STYLE_COMMANDS_PANEL_BORDER": _rich_style("muted"),
|
|
51
|
+
"STYLE_TYPES_SEPARATOR": _rich_style("muted"),
|
|
52
|
+
"STYLE_REQUIRED_SHORT": _rich_style("error"),
|
|
53
|
+
"STYLE_REQUIRED_LONG": _rich_style("error"),
|
|
54
|
+
"STYLE_ERRORS_PANEL_BORDER": _rich_style("error"),
|
|
55
|
+
"STYLE_ERRORS_SUGGESTION": _rich_style("ok", bold=True),
|
|
56
|
+
"STYLE_ABORTED": _rich_style("error"),
|
|
57
|
+
"STYLE_DEPRECATED": _rich_style("error"),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_RICH_HELP_EN = "Try '{command_path} {help_option}' for help."
|
|
61
|
+
_RICH_HELP_RU = "Попробуйте '{command_path} {help_option}' для справки."
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def apply_palette() -> None:
|
|
65
|
+
from typer import rich_utils
|
|
66
|
+
|
|
67
|
+
for name, value in PALETTE_OVERRIDES.items():
|
|
68
|
+
setattr(rich_utils, name, value)
|
|
69
|
+
rich_utils.RICH_HELP = _RICH_HELP_EN
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def disable_click_colorama() -> None:
|
|
73
|
+
# click.echo wraps console streams in colorama, which converts ANSI to
|
|
74
|
+
# legacy Win32 16-color and mangles 38;2/OSC-8 (colorama#217); patched in
|
|
75
|
+
# both namespaces because echo imported the name into typer._click.utils.
|
|
76
|
+
from typer._click import _compat
|
|
77
|
+
from typer._click import utils as click_utils
|
|
78
|
+
|
|
79
|
+
def _passthrough(stream: object, color: bool | None = None) -> object:
|
|
80
|
+
return stream
|
|
81
|
+
|
|
82
|
+
_compat.auto_wrap_for_ansi = _passthrough
|
|
83
|
+
click_utils.auto_wrap_for_ansi = _passthrough
|
|
84
|
+
|
|
85
|
+
_EXTRA_ARGS = re.compile(r"^Got unexpected extra argument\(s\) \((?P<args>.*)\)$")
|
|
86
|
+
_REQUIRES_ARG = re.compile(
|
|
87
|
+
r"^Option (?P<opt>'[^']+') requires (?P<what>an argument\.|\d+ arguments\.)$"
|
|
88
|
+
)
|
|
89
|
+
_NO_VALUE = re.compile(r"^Option (?P<opt>'[^']+') does not take a value\.$")
|
|
90
|
+
_NOT_VALID_BOOL = re.compile(
|
|
91
|
+
r"^(?P<value>.+) is not a valid boolean\. Recognized values: (?P<states>.*)$"
|
|
92
|
+
)
|
|
93
|
+
_NOT_VALID = re.compile(r"^(?P<value>.+) is not a valid (?P<type>[A-Za-z ]+)\.$")
|
|
94
|
+
|
|
95
|
+
_TYPE_NAMES = {
|
|
96
|
+
"integer": "целым числом",
|
|
97
|
+
"int": "целым числом",
|
|
98
|
+
"float": "числом",
|
|
99
|
+
"number": "числом",
|
|
100
|
+
"boolean": "логическим значением",
|
|
101
|
+
"bool": "логическим значением",
|
|
102
|
+
"uuid": "UUID",
|
|
103
|
+
"path": "путём",
|
|
104
|
+
"file": "файлом",
|
|
105
|
+
"directory": "каталогом",
|
|
106
|
+
"text": "текстом",
|
|
107
|
+
"string": "строкой",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def translate_message(message: str) -> str:
|
|
112
|
+
"""Map the hard-coded English parser/type messages onto Russian."""
|
|
113
|
+
match = _EXTRA_ARGS.match(message)
|
|
114
|
+
if match:
|
|
115
|
+
return f"Получены неожиданные лишние аргументы ({match['args']})"
|
|
116
|
+
match = _REQUIRES_ARG.match(message)
|
|
117
|
+
if match:
|
|
118
|
+
what = "аргумент" if match["what"] == "an argument." else "аргументов: " + match["what"].split()[0]
|
|
119
|
+
return f"Опция {match['opt']} требует {what}."
|
|
120
|
+
match = _NO_VALUE.match(message)
|
|
121
|
+
if match:
|
|
122
|
+
return f"Опция {match['opt']} не принимает значение."
|
|
123
|
+
match = _NOT_VALID_BOOL.match(message)
|
|
124
|
+
if match:
|
|
125
|
+
return (
|
|
126
|
+
f"{match['value']} не является допустимым логическим значением. "
|
|
127
|
+
f"Допустимые значения: {match['states']}"
|
|
128
|
+
)
|
|
129
|
+
match = _NOT_VALID.match(message)
|
|
130
|
+
if match:
|
|
131
|
+
kind = _TYPE_NAMES.get(match["type"].lower())
|
|
132
|
+
if kind:
|
|
133
|
+
return f"{match['value']} не является допустимым {kind}."
|
|
134
|
+
return f"{match['value']} — недопустимое значение ({match['type']})."
|
|
135
|
+
return message
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _set(target: object, name: str, value: object) -> None:
|
|
139
|
+
_BACKUP.append((target, name, getattr(target, name)))
|
|
140
|
+
setattr(target, name, value)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def apply_ru() -> None:
|
|
144
|
+
if _BACKUP:
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
from typer import rich_utils
|
|
148
|
+
from typer._click import decorators as click_decorators
|
|
149
|
+
from typer._click import exceptions as click_exceptions
|
|
150
|
+
from typer._click import formatting as click_formatting
|
|
151
|
+
|
|
152
|
+
_set(rich_utils, "ARGUMENTS_PANEL_TITLE", "Аргументы")
|
|
153
|
+
_set(rich_utils, "OPTIONS_PANEL_TITLE", "Опции")
|
|
154
|
+
_set(rich_utils, "COMMANDS_PANEL_TITLE", "Команды")
|
|
155
|
+
_set(rich_utils, "ERRORS_PANEL_TITLE", "Ошибка")
|
|
156
|
+
_set(rich_utils, "ABORTED_TEXT", "Прервано.")
|
|
157
|
+
_set(rich_utils, "DEFAULT_STRING", "[по умолчанию: {}]")
|
|
158
|
+
_set(rich_utils, "REQUIRED_LONG_STRING", "[обязательно]")
|
|
159
|
+
_set(rich_utils, "RICH_HELP", _RICH_HELP_RU)
|
|
160
|
+
|
|
161
|
+
original_write_usage = click_formatting.HelpFormatter.write_usage
|
|
162
|
+
|
|
163
|
+
def write_usage(self, prog, args="", prefix=None): # type: ignore[no-untyped-def]
|
|
164
|
+
if prefix is None:
|
|
165
|
+
prefix = "Использование: "
|
|
166
|
+
original_write_usage(self, prog, args, prefix)
|
|
167
|
+
|
|
168
|
+
_set(click_formatting.HelpFormatter, "write_usage", write_usage)
|
|
169
|
+
|
|
170
|
+
original_help_option = click_decorators.help_option
|
|
171
|
+
|
|
172
|
+
def help_option(param_decls): # type: ignore[no-untyped-def]
|
|
173
|
+
decorate = original_help_option(param_decls)
|
|
174
|
+
|
|
175
|
+
def apply(command): # type: ignore[no-untyped-def]
|
|
176
|
+
decorate(command)
|
|
177
|
+
command.params[-1].help = "Показать это сообщение и выйти."
|
|
178
|
+
return command
|
|
179
|
+
|
|
180
|
+
return apply
|
|
181
|
+
|
|
182
|
+
_set(click_decorators, "help_option", help_option)
|
|
183
|
+
|
|
184
|
+
def click_exception_show(self, file=None): # type: ignore[no-untyped-def]
|
|
185
|
+
if file is None:
|
|
186
|
+
file = click_exceptions.get_text_stderr()
|
|
187
|
+
click_exceptions.echo(
|
|
188
|
+
f"Ошибка: {self.format_message()}", file=file, color=self.show_color
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
_set(click_exceptions.ClickException, "show", click_exception_show)
|
|
192
|
+
|
|
193
|
+
def usage_error_show(self, file=None): # type: ignore[no-untyped-def]
|
|
194
|
+
if file is None:
|
|
195
|
+
file = click_exceptions.get_text_stderr()
|
|
196
|
+
color = None
|
|
197
|
+
hint = ""
|
|
198
|
+
if self.ctx is not None and self.ctx.command.get_help_option(self.ctx) is not None:
|
|
199
|
+
command = self.ctx.command_path
|
|
200
|
+
option = self.ctx.help_option_names[0]
|
|
201
|
+
hint = f"Попробуйте '{command} {option}' для справки.\n"
|
|
202
|
+
if self.ctx is not None:
|
|
203
|
+
color = self.ctx.color
|
|
204
|
+
click_exceptions.echo(
|
|
205
|
+
f"{self.ctx.get_usage()}\n{hint}", file=file, color=color
|
|
206
|
+
)
|
|
207
|
+
click_exceptions.echo(
|
|
208
|
+
f"Ошибка: {self.format_message()}", file=file, color=color
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
_set(click_exceptions.UsageError, "show", usage_error_show)
|
|
212
|
+
|
|
213
|
+
def usage_error_format_message(self): # type: ignore[no-untyped-def]
|
|
214
|
+
return translate_message(self.message)
|
|
215
|
+
|
|
216
|
+
_set(click_exceptions.UsageError, "format_message", usage_error_format_message)
|
|
217
|
+
|
|
218
|
+
def bad_parameter_format_message(self): # type: ignore[no-untyped-def]
|
|
219
|
+
if self.param_hint is not None:
|
|
220
|
+
param_hint = self.param_hint
|
|
221
|
+
elif self.param is not None:
|
|
222
|
+
param_hint = self.param.get_error_hint(self.ctx)
|
|
223
|
+
else:
|
|
224
|
+
return f"Неверное значение: {translate_message(self.message)}"
|
|
225
|
+
hint = click_exceptions._join_param_hints(param_hint)
|
|
226
|
+
return f"Неверное значение для {hint}: {translate_message(self.message)}"
|
|
227
|
+
|
|
228
|
+
_set(click_exceptions.BadParameter, "format_message", bad_parameter_format_message)
|
|
229
|
+
|
|
230
|
+
def missing_parameter_format_message(self): # type: ignore[no-untyped-def]
|
|
231
|
+
if self.param_hint is not None:
|
|
232
|
+
param_hint = self.param_hint
|
|
233
|
+
elif self.param is not None:
|
|
234
|
+
param_hint = self.param.get_error_hint(self.ctx)
|
|
235
|
+
else:
|
|
236
|
+
param_hint = None
|
|
237
|
+
param_hint = click_exceptions._join_param_hints(param_hint)
|
|
238
|
+
param_hint = f" {param_hint}" if param_hint else ""
|
|
239
|
+
|
|
240
|
+
param_type = self.param_type
|
|
241
|
+
if param_type is None and self.param is not None:
|
|
242
|
+
param_type = self.param.param_type_name
|
|
243
|
+
|
|
244
|
+
msg = self.message
|
|
245
|
+
if self.param is not None:
|
|
246
|
+
msg_extra = self.param.type.get_missing_message(param=self.param, ctx=self.ctx)
|
|
247
|
+
if msg_extra:
|
|
248
|
+
msg = f"{msg}. {msg_extra}" if msg else msg_extra
|
|
249
|
+
msg = f" {msg}" if msg else ""
|
|
250
|
+
|
|
251
|
+
missing = {
|
|
252
|
+
"argument": "Отсутствует аргумент",
|
|
253
|
+
"option": "Отсутствует опция",
|
|
254
|
+
"parameter": "Отсутствует параметр",
|
|
255
|
+
}.get(param_type, f"Отсутствует {param_type}")
|
|
256
|
+
return f"{missing}{param_hint}.{msg}"
|
|
257
|
+
|
|
258
|
+
_set(
|
|
259
|
+
click_exceptions.MissingParameter, "format_message", missing_parameter_format_message
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def no_such_option_format_message(self): # type: ignore[no-untyped-def]
|
|
263
|
+
base = f"Нет такой опции: {self.option_name}"
|
|
264
|
+
if not self.possibilities:
|
|
265
|
+
return base
|
|
266
|
+
possibility_str = ", ".join(sorted(self.possibilities))
|
|
267
|
+
return f"{base} (Возможные варианты: {possibility_str})"
|
|
268
|
+
|
|
269
|
+
_set(click_exceptions.NoSuchOption, "format_message", no_such_option_format_message)
|
|
270
|
+
|
|
271
|
+
def file_error_format_message(self): # type: ignore[no-untyped-def]
|
|
272
|
+
return f"Не удалось открыть файл {self.ui_filename!r}: {self.message}"
|
|
273
|
+
|
|
274
|
+
_set(click_exceptions.FileError, "format_message", file_error_format_message)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def revert_ru() -> None:
|
|
278
|
+
"""Undo every patch made by the last apply_ru(); no-op when unpatched."""
|
|
279
|
+
while _BACKUP:
|
|
280
|
+
target, name, original = _BACKUP.pop()
|
|
281
|
+
setattr(target, name, original)
|