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.
Files changed (62) hide show
  1. xrayvpn/__init__.py +3 -0
  2. xrayvpn/__main__.py +6 -0
  3. xrayvpn/cli/__init__.py +0 -0
  4. xrayvpn/cli/entry.py +15 -0
  5. xrayvpn/cli/l10n_typer.py +281 -0
  6. xrayvpn/cli/main.py +1036 -0
  7. xrayvpn/cli/prompts.py +53 -0
  8. xrayvpn/cli/repl.py +408 -0
  9. xrayvpn/cli/service.py +444 -0
  10. xrayvpn/cli/terminal_host.py +282 -0
  11. xrayvpn/cli/theme.py +297 -0
  12. xrayvpn/core/__init__.py +0 -0
  13. xrayvpn/core/config.py +60 -0
  14. xrayvpn/core/conn.py +186 -0
  15. xrayvpn/core/deploy_lock.py +80 -0
  16. xrayvpn/core/execution/__init__.py +0 -0
  17. xrayvpn/core/execution/base.py +46 -0
  18. xrayvpn/core/execution/local.py +273 -0
  19. xrayvpn/core/execution/remote.py +329 -0
  20. xrayvpn/core/inventory.py +145 -0
  21. xrayvpn/core/manifest.py +57 -0
  22. xrayvpn/core/runtime_paths.py +257 -0
  23. xrayvpn/core/service_actions.py +209 -0
  24. xrayvpn/core/transport/__init__.py +0 -0
  25. xrayvpn/core/transport/remote.py +145 -0
  26. xrayvpn/core/update_check.py +147 -0
  27. xrayvpn/core/wsl.py +92 -0
  28. xrayvpn/i18n.py +55 -0
  29. xrayvpn/payload/config/README.en.md +56 -0
  30. xrayvpn/payload/config/README.md +56 -0
  31. xrayvpn/payload/config/settings.yml +79 -0
  32. xrayvpn/payload/deploy.yml +15 -0
  33. xrayvpn/payload/inventory.yml.example +8 -0
  34. xrayvpn/payload/roles/xray_vpn/handlers/main.yml +23 -0
  35. xrayvpn/payload/roles/xray_vpn/meta/main.yml +19 -0
  36. xrayvpn/payload/roles/xray_vpn/tasks/keys.yml +421 -0
  37. xrayvpn/payload/roles/xray_vpn/tasks/logs.yml +19 -0
  38. xrayvpn/payload/roles/xray_vpn/tasks/main.yml +169 -0
  39. xrayvpn/payload/roles/xray_vpn/tasks/obs.yml +60 -0
  40. xrayvpn/payload/roles/xray_vpn/tasks/runtime_docker.yml +33 -0
  41. xrayvpn/payload/roles/xray_vpn/tasks/runtime_native.yml +69 -0
  42. xrayvpn/payload/roles/xray_vpn/tasks/runtime_setup.yml +31 -0
  43. xrayvpn/payload/roles/xray_vpn/tasks/smoke.yml +98 -0
  44. xrayvpn/payload/roles/xray_vpn/tasks/warp.yml +107 -0
  45. xrayvpn/payload/roles/xray_vpn/tasks/watchdog.yml +66 -0
  46. xrayvpn/payload/roles/xray_vpn/templates/clash_meta.yaml.j2 +119 -0
  47. xrayvpn/payload/roles/xray_vpn/templates/client_config.json.j2 +46 -0
  48. xrayvpn/payload/roles/xray_vpn/templates/journald-60-xray-ansible.conf.j2 +6 -0
  49. xrayvpn/payload/roles/xray_vpn/templates/xray-obs-snapshot.service.j2 +6 -0
  50. xrayvpn/payload/roles/xray_vpn/templates/xray-obs-snapshot.sh.j2 +28 -0
  51. xrayvpn/payload/roles/xray_vpn/templates/xray-obs-snapshot.timer.j2 +10 -0
  52. xrayvpn/payload/roles/xray_vpn/templates/xray-watchdog.service.j2 +6 -0
  53. xrayvpn/payload/roles/xray_vpn/templates/xray-watchdog.sh.j2 +111 -0
  54. xrayvpn/payload/roles/xray_vpn/templates/xray-watchdog.timer.j2 +10 -0
  55. xrayvpn/payload/roles/xray_vpn/templates/xray.service.docker.j2 +26 -0
  56. xrayvpn/payload/roles/xray_vpn/templates/xray.service.native.j2 +26 -0
  57. xrayvpn/payload/roles/xray_vpn/templates/xray_config.json.j2 +98 -0
  58. xrayvpn/text/__init__.py +623 -0
  59. xrayvpn-0.4.1.dist-info/METADATA +174 -0
  60. xrayvpn-0.4.1.dist-info/RECORD +62 -0
  61. xrayvpn-0.4.1.dist-info/WHEEL +4 -0
  62. xrayvpn-0.4.1.dist-info/entry_points.txt +2 -0
xrayvpn/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """xrayvpn — Python client for provisioning Xray VLESS + REALITY VPN servers."""
2
+
3
+ __version__ = "0.4.1"
xrayvpn/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Enable `python -m xrayvpn` without a prior install; also the binary build entry."""
2
+
3
+ from xrayvpn.cli.entry import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
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)