rottenstuff commited on
Commit
7a93156
·
verified ·
1 Parent(s): 3dcdef7

Delete src

Browse files
Files changed (4) hide show
  1. src/__init__.py +0 -75
  2. src/_config.py +0 -47
  3. src/_tunnels.py +0 -168
  4. src/_utils.py +0 -256
src/__init__.py DELETED
@@ -1,75 +0,0 @@
1
- """
2
- colab-proxy — утилиты для туннелирования веб-интерфейсов в Google Colab.
3
-
4
- Быстрый старт:
5
- from colab_proxy import get_share_link
6
- link = get_share_link('cloudflared', 7860)
7
- print(link)
8
- """
9
-
10
- from ._config import WORK_FOLDER
11
- from ._utils import run, download
12
- from ._tunnels import (
13
- get_tmole_url,
14
- get_tunwg_url,
15
- get_cloudflared_url,
16
- get_localt_url,
17
- get_gradio_url,
18
- get_native_url,
19
- )
20
-
21
- __version__ = '1.0.0'
22
- __all__ = [
23
- 'get_share_link',
24
- 'try_all',
25
- 'proxies_functions',
26
- 'get_tmole_url',
27
- 'get_tunwg_url',
28
- 'get_cloudflared_url',
29
- 'get_localt_url',
30
- 'get_gradio_url',
31
- 'get_native_url',
32
- 'download',
33
- 'run',
34
- 'WORK_FOLDER',
35
- ]
36
-
37
- proxies_functions: dict = {
38
- 'tmole': get_tmole_url,
39
- 'tunwg': get_tunwg_url,
40
- 'cloudflared': get_cloudflared_url,
41
- 'localt': get_localt_url,
42
- 'gradio': get_gradio_url,
43
- 'native': get_native_url,
44
- }
45
-
46
-
47
- def try_all(port: int) -> str:
48
- """Запускает все доступные туннели и возвращает все полученные ссылки."""
49
- results: list[str] = []
50
- for name, func in proxies_functions.items():
51
- try:
52
- results.append(func(port))
53
- except Exception as e:
54
- print(f'{name}: {e}')
55
- return '\n'.join(results)
56
-
57
-
58
- def get_share_link(host: str, port: int) -> str:
59
- """
60
- Получить публичную ссылку на локальный порт через указанный туннель.
61
-
62
- Args:
63
- host: Имя провайдера: 'cloudflared', 'tmole', 'tunwg',
64
- 'localt', 'gradio', 'native' — или 'all' для всех сразу.
65
- port: Локальный порт веб-интерфейса.
66
-
67
- Returns:
68
- Публичная URL-ссылка (или несколько, если host='all').
69
- """
70
- if host in proxies_functions:
71
- return proxies_functions[host](port)
72
- if host == 'all':
73
- return try_all(port)
74
- available = ', '.join(proxies_functions.keys())
75
- return f'провайдер "{host}" не найден. Доступные: {available}'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/_config.py DELETED
@@ -1,47 +0,0 @@
1
- from os import environ
2
- from pathlib import Path
3
-
4
- # Переопределяется переменной окружения COLAB_PROXY_WORK_DIR
5
- WORK_FOLDER = Path(environ.get('COLAB_PROXY_WORK_DIR', '/content/.config'))
6
-
7
- claudflare_bin = WORK_FOLDER / 'claudflared'
8
- tmole_bin = WORK_FOLDER / 'tmole'
9
- tunwg_bin = WORK_FOLDER / 'tunwg'
10
- go_localt_bin = WORK_FOLDER / 'go_localt'
11
- gradio_bin = WORK_FOLDER / 'frpc_linux_amd64'
12
- colab_native_url = WORK_FOLDER / 'colab_url.txt'
13
- links_file = WORK_FOLDER / 'links.txt'
14
-
15
- HEADERS = {
16
- 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
17
- 'accept-language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
18
- 'cache-control': 'no-cache',
19
- 'device-memory': '8',
20
- 'downlink': '10.0',
21
- 'dpr': '1.5',
22
- 'ect': '4g',
23
- 'origin': 'https://colab.research.google.com',
24
- 'pragma': 'no-cache',
25
- 'priority': 'u=0, i',
26
- 'referer': 'https://colab.research.google.com',
27
- 'rtt': '200',
28
- 'sec-ch-ua': '"Google Chrome";v="148", "Not?A)Brand";v="24", "Chromium";v="148"',
29
- 'sec-ch-ua-arch': '"x86"',
30
- 'sec-ch-ua-bitness': '"64"',
31
- 'sec-ch-ua-full-version': '"148.0.7778.168"',
32
- 'sec-ch-ua-full-version-list': '"Google Chrome";v="148.0.7778.168", "Not?A)Brand";v="24.0.0.0", "Chromium";v="148.0.7778.168"',
33
- 'sec-ch-ua-mobile': '?0',
34
- 'sec-ch-ua-model': '""',
35
- 'sec-ch-ua-platform': '"Windows"',
36
- 'sec-ch-ua-platform-version': '"19.0.0"',
37
- 'sec-ch-ua-wow64': '?0',
38
- 'sec-ch-viewport-height': '592',
39
- 'sec-ch-viewport-width': '1216',
40
- 'sec-fetch-dest': 'document',
41
- 'sec-fetch-mode': 'navigate',
42
- 'sec-fetch-site': 'same-origin',
43
- 'sec-fetch-user': '?1',
44
- 'upgrade-insecure-requests': '1',
45
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.168 Safari/537.36',
46
- 'viewport-width': '1216'
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/_tunnels.py DELETED
@@ -1,168 +0,0 @@
1
- from atexit import register as exit_register
2
- from pathlib import Path
3
- from re import search
4
- from subprocess import PIPE, Popen
5
- from time import sleep
6
-
7
- from requests import get as get_url
8
-
9
- from ._config import (
10
- WORK_FOLDER,
11
- claudflare_bin, tmole_bin, tunwg_bin,
12
- go_localt_bin, gradio_bin, links_file,
13
- )
14
- from ._utils import (
15
- download, unpack_archive, run,
16
- is_process_running, terminate_process,
17
- move_path, is_ipv4,
18
- )
19
-
20
-
21
- def get_revproxy_url(
22
- bin_url: str,
23
- need_unpack: bool,
24
- bin_path: Path,
25
- start_commands: list,
26
- read_from_stderr: bool,
27
- lines_to_read: int,
28
- url_pattern: str = r'https://\S+',
29
- write_link: bool = False,
30
- ) -> str:
31
- if not bin_path.exists():
32
- files_before = {f.name for f in WORK_FOLDER.iterdir() if f.is_file()}
33
-
34
- if need_unpack:
35
- unpack_archive(download(bin_url, progress=False), WORK_FOLDER, rm_archive=True)
36
- else:
37
- download(bin_url, save_path=bin_path.parent, progress=False)
38
-
39
- files_after = {f.name for f in WORK_FOLDER.iterdir() if f.is_file()}
40
- new_files = files_after - files_before
41
-
42
- if len(new_files) == 1:
43
- move_path(WORK_FOLDER / new_files.pop(), bin_path)
44
- else:
45
- raise RuntimeError(
46
- f'ошибка при определении нового файла после распаковки!\n'
47
- f'ожидался один новый файл, а появились: {", ".join(new_files)}'
48
- )
49
-
50
- bin_path.chmod(0o777)
51
-
52
- if is_process_running(bin_path.name):
53
- run(f'pkill -f {bin_path.name}')
54
-
55
- process = Popen(start_commands, stdout=PIPE, stderr=PIPE, text=True)
56
- stream = process.stderr if read_from_stderr else process.stdout
57
- lines: list[str] = []
58
-
59
- for _ in range(lines_to_read):
60
- line = stream.readline()
61
- if not line:
62
- break
63
- lines.append(line.strip())
64
-
65
- ipv4 = next((ln for ln in lines if is_ipv4(ln)), None)
66
- urls = [search(url_pattern, ln).group() for ln in lines if search(url_pattern, ln)]
67
-
68
- if urls:
69
- exit_register(terminate_process, bin_path.name, process)
70
- if write_link:
71
- links_file.write_text(urls[0])
72
- return f'{urls[0]}\n пароль(IP): {ipv4}' if ipv4 else urls[0]
73
-
74
- run(f'pkill -f {bin_path.name}')
75
- raise RuntimeError(
76
- f'ссылку получить не удалось, вывод работы бинарника:\n' + '\n'.join(lines)
77
- )
78
-
79
-
80
- # ---------------------------------------------------------------------------
81
- # Конкретные провайдеры
82
- # ---------------------------------------------------------------------------
83
-
84
- def get_tmole_url(port: int) -> str:
85
- return get_revproxy_url(
86
- bin_url='https://tunnelmole.com/downloads/tmole-linux.gz',
87
- need_unpack=True,
88
- bin_path=tmole_bin,
89
- start_commands=[str(tmole_bin), str(port)],
90
- read_from_stderr=False,
91
- lines_to_read=5,
92
- )
93
-
94
-
95
- def get_tunwg_url(port: int) -> str:
96
- return get_revproxy_url(
97
- bin_url='https://github.com/ntnj/tunwg/releases/latest/download/tunwg',
98
- need_unpack=False,
99
- bin_path=tunwg_bin,
100
- start_commands=[str(tunwg_bin), f'--forward=http://127.0.0.1:{port}'],
101
- read_from_stderr=True,
102
- lines_to_read=2,
103
- )
104
-
105
-
106
- def get_cloudflared_url(port: int) -> str:
107
- return get_revproxy_url(
108
- bin_url='https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64',
109
- need_unpack=False,
110
- bin_path=claudflare_bin,
111
- start_commands=[str(claudflare_bin), 'tunnel', '--url', f'http://127.0.0.1:{port}'],
112
- read_from_stderr=True,
113
- lines_to_read=6,
114
- url_pattern=r'(?P<url>https?://\S+\.trycloudflare\.com)',
115
- )
116
-
117
-
118
- def get_localt_url(port: int) -> str:
119
- return get_revproxy_url(
120
- bin_url='https://huggingface.co/prolapse/go_localt/resolve/main/go_localt',
121
- need_unpack=False,
122
- bin_path=go_localt_bin,
123
- start_commands=[str(go_localt_bin), str(port)],
124
- read_from_stderr=False,
125
- lines_to_read=2,
126
- )
127
-
128
-
129
- def get_gradio_url(port: int) -> str:
130
- max_attempts = 3
131
- last_error: Exception | None = None
132
-
133
- for attempt in range(max_attempts):
134
- try:
135
- resp = get_url('https://api.gradio.app/v2/tunnel-request', timeout=10)
136
- resp.raise_for_status()
137
- info = resp.json()[0]
138
- remote_host, remote_port = info['host'], int(info['port'])
139
- cmd = [
140
- str(gradio_bin), 'http', '-n', 'random',
141
- '-l', str(port), '-i', '127.0.0.1',
142
- '--uc', '--sd', 'random', '--ue',
143
- '--server_addr', f'{remote_host}:{remote_port}',
144
- '--disable_log_color',
145
- ]
146
- return get_revproxy_url(
147
- bin_url='https://cdn-media.huggingface.co/frpc-gradio-0.1/frpc_linux_amd64',
148
- need_unpack=False,
149
- bin_path=gradio_bin,
150
- start_commands=cmd,
151
- read_from_stderr=False,
152
- lines_to_read=3,
153
- )
154
- except Exception as e:
155
- last_error = e
156
- if attempt < max_attempts - 1:
157
- print(f'попытка {attempt + 1} получить ссылку градио провалилась: {e}. пробуем ещё...')
158
- sleep(5)
159
-
160
- raise RuntimeError(f'после {max_attempts} попыток поднять туннель градио не удалось: {last_error}')
161
-
162
-
163
- def get_native_url(port: int) -> str:
164
- try:
165
- from google.colab.output import eval_js # type: ignore[import]
166
- except ImportError:
167
- raise RuntimeError('get_native_url доступен только в среде Google Colab.')
168
- return eval_js(f'google.colab.kernel.proxyPort({port})')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/_utils.py DELETED
@@ -1,256 +0,0 @@
1
- from os import name as os_name
2
- from pathlib import Path
3
- from re import compile, search
4
- from shutil import move
5
- from subprocess import PIPE, Popen, STDOUT, check_output, CalledProcessError
6
- from sys import stdout
7
- from time import time
8
- from urllib.parse import unquote, urlparse
9
-
10
- from requests import get as get_url, head as get_head
11
- from requests.structures import CaseInsensitiveDict
12
-
13
- from ._config import WORK_FOLDER, HEADERS
14
-
15
-
16
- # ---------------------------------------------------------------------------
17
- # Архивы
18
- # ---------------------------------------------------------------------------
19
-
20
- def determine_archive_format(filepath: str | Path) -> str | None:
21
- filepath = Path(filepath)
22
- zip_sig = bytes([0x50, 0x4B, 0x03, 0x04])
23
- seven_z = bytes([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])
24
- lzma_xz = bytes([0xFD, 0x37, 0x7A, 0x58, 0x5A])
25
- tgz = bytes([0x1F, 0x8B])
26
- tbz = bytes([0x42, 0x5A, 0x68])
27
- ustar = bytes([0x75, 0x73, 0x74, 0x61, 0x72])
28
-
29
- with filepath.open('rb') as fh:
30
- header = fh.read(262)
31
-
32
- if header.startswith(zip_sig):
33
- return 'zip'
34
- if header.startswith(seven_z) or header.startswith(lzma_xz):
35
- return '7z'
36
- if header.startswith(tgz):
37
- return 'tar.gz'
38
- if header.startswith(tbz):
39
- return 'tar.bz2'
40
- if header[0x101:0x101 + len(ustar)] == ustar:
41
- return 'tar'
42
- return None
43
-
44
-
45
- def unpack_archive(archive_path: str | Path, dest_path: str | Path, rm_archive: bool = True) -> None:
46
- if str(archive_path).startswith(('https://', 'http://')):
47
- archive_path = download(archive_path, save_path=WORK_FOLDER, progress=False)
48
-
49
- archive_path = Path(archive_path)
50
- dest_path = Path(dest_path)
51
-
52
- if not archive_path.exists():
53
- raise RuntimeError(f'архив {archive_path} не найден.')
54
-
55
- fmt = determine_archive_format(archive_path)
56
-
57
- def _rm() -> None:
58
- if rm_archive:
59
- archive_path.unlink(missing_ok=True)
60
-
61
- try:
62
- if fmt == '7z' or archive_path.suffix == '.7z':
63
- run(f'7z -bso0 -bd -slp -y x "{archive_path}" -o"{dest_path}"')
64
- elif fmt == 'tar' or archive_path.suffix == '.tar':
65
- run(f'tar -xvpf "{archive_path}" -C "{dest_path}"')
66
- elif fmt in ('tar.gz', 'tar.bz2') or archive_path.suffix in ('.tar.gz', '.tar.bz2', '.tar.xz'):
67
- result = run(f'tar -xvzpf "{archive_path}" -C "{dest_path}"')
68
- if result['status_code'] != 0:
69
- run(f'gzip -d "{archive_path}"', dest_path)
70
- elif fmt == 'zip' or archive_path.suffix == '.zip':
71
- run(f'unzip "{archive_path}" -d "{dest_path}"')
72
- else:
73
- run(f'7z -bso0 -bd -slp -y x "{archive_path}" -o"{dest_path}"')
74
- _rm()
75
- except Exception:
76
- try:
77
- run(f'7z -bso0 -bd -mmt4 -slp -y x "{archive_path}" -o"{dest_path}"')
78
- _rm()
79
- except Exception:
80
- raise RuntimeError(
81
- f'формат архива {archive_path.suffix} не определён. '
82
- f'Задайте формат вручную через determine_archive_format.'
83
- )
84
-
85
-
86
- # ---------------------------------------------------------------------------
87
- # Процессы
88
- # ---------------------------------------------------------------------------
89
-
90
- def run(command: str, cwd: str | Path | None = None, live_output: bool = False) -> dict:
91
- process = Popen(command, shell=True, cwd=cwd, stdout=PIPE, stderr=STDOUT)
92
- _encodings = ['utf-8', 'iso-8859-5', 'windows-1251', 'cp866', 'koi8-r', 'mac_cyrillic']
93
- _progress_pattern = r'\d+%|\d+/\d+'
94
-
95
- def _decode(data: bytes) -> str:
96
- for enc in _encodings:
97
- try:
98
- return data.decode(enc)
99
- except UnicodeDecodeError:
100
- continue
101
- return data.decode('utf-8', errors='replace')
102
-
103
- final_output: list[str] = []
104
- last_progress = ''
105
-
106
- for raw_line in iter(process.stdout.readline, b''):
107
- line = _decode(raw_line.strip())
108
- if search(_progress_pattern, line):
109
- last_progress = line
110
- if live_output:
111
- stdout.write('\r' + line)
112
- else:
113
- final_output.append(line)
114
- if live_output:
115
- stdout.write(line + '\n')
116
- if live_output:
117
- stdout.flush()
118
-
119
- process.wait()
120
- if last_progress:
121
- final_output.append(last_progress)
122
-
123
- return {
124
- 'status_code': process.returncode,
125
- 'output': '\n'.join(final_output),
126
- }
127
-
128
-
129
- def is_process_running(process_name: str | Path) -> bool:
130
- try:
131
- output = check_output(['pgrep', '-f', str(process_name)], text=True)
132
- return bool(output.strip())
133
- except CalledProcessError:
134
- return False
135
-
136
-
137
- def terminate_process(process_name: str, process_obj: Popen) -> None:
138
- try:
139
- process_obj.stdout.close()
140
- except Exception:
141
- pass
142
- try:
143
- process_obj.stderr.close()
144
- except Exception:
145
- pass
146
- process_obj.terminate()
147
- run(f'pkill -f {process_name}')
148
-
149
-
150
- def move_path(old_path: Path | str, new_path: Path | str) -> None:
151
- old, new = Path(old_path), Path(new_path)
152
- # BUGFIX: оригинал проверял old_path.exists() — падало если old_path это str
153
- if not old.exists():
154
- raise RuntimeError(f'не найден исходный путь для перемещения: {old}')
155
- try:
156
- old.replace(new)
157
- except Exception:
158
- try:
159
- move(old, new)
160
- except Exception:
161
- if os_name == 'posix':
162
- run(f'mv "{old}" "{new}"')
163
- else:
164
- run(f'move "{old}" "{new}"')
165
-
166
-
167
- # ---------------------------------------------------------------------------
168
- # Сеть
169
- # ---------------------------------------------------------------------------
170
-
171
- def is_ipv4(address: str) -> bool:
172
- pattern = compile(
173
- r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$'
174
- )
175
- return bool(pattern.match(address))
176
-
177
-
178
- def is_valid_url(url: str) -> bool:
179
- for attempt in range(2):
180
- try:
181
- with get_url(url, headers=HEADERS, allow_redirects=True, stream=True, timeout=10) as resp:
182
- if 200 <= resp.status_code < 300:
183
- return True
184
- except Exception:
185
- try:
186
- resp = get_head(url, headers=HEADERS, allow_redirects=True, timeout=10)
187
- if 200 <= resp.status_code < 300:
188
- return True
189
- except Exception:
190
- pass
191
- else:
192
- break
193
- return False
194
-
195
-
196
- def get_filename_from_headers(headers: CaseInsensitiveDict) -> str | None:
197
- cd = headers.get('content-disposition')
198
- if not cd:
199
- return headers.get('filename')
200
-
201
- for part in cd.split(';'):
202
- part = part.strip()
203
- if part.startswith('filename*='):
204
- encoding, _, encoded = part[len('filename*='):].partition("''")
205
- return unquote(encoded, encoding=encoding)
206
- if part.startswith('filename='):
207
- return part[len('filename='):].strip('"')
208
- return None
209
-
210
-
211
- def download(
212
- url: str,
213
- filename: str | Path | None = None,
214
- save_path: str | Path | None = None,
215
- progress: bool = True,
216
- ) -> Path:
217
- # Поддержка --header= суффикса
218
- extra_headers: dict | None = None
219
- url_parts = url.replace('"', '').replace("'", '').split('--header=')
220
- if len(url_parts) > 1:
221
- url = url_parts[0].strip()
222
- key, _, value = url_parts[1].partition(':') # BUGFIX: partition вместо split(':')[1]
223
- extra_headers = {key.strip(): value.strip()}
224
-
225
- if not is_valid_url(url):
226
- raise RuntimeError(f'недействительная ссылка на файл: {url}')
227
-
228
- save_path = Path(save_path) if save_path else Path.cwd()
229
- save_path.mkdir(parents=True, exist_ok=True)
230
-
231
- with get_url(url, stream=True, allow_redirects=True, headers=extra_headers) as req:
232
- file_size = int(req.headers.get('content-length', 0))
233
- file_name = filename or get_filename_from_headers(req.headers) or Path(urlparse(req.url).path).name
234
- file_path = save_path / file_name
235
- chunk_size = max(4096, file_size // 2000) if file_size else 4096
236
-
237
- downloaded = 0
238
- start = time()
239
- try:
240
- with open(file_path, 'wb') as fp:
241
- for chunk in req.iter_content(chunk_size=chunk_size):
242
- if chunk:
243
- fp.write(chunk)
244
- if progress and file_size: # BUGFIX: guard деления на 0
245
- downloaded += len(chunk)
246
- elapsed = time() - start
247
- print(
248
- f'\rзагрузка {file_name}: {downloaded / file_size * 100:.2f}% | {elapsed:.2f} сек.',
249
- end='',
250
- )
251
- except Exception as e:
252
- raise RuntimeError(f'не удалось загрузить файл по ссылке {url}:\n{e}')
253
-
254
- if progress and file_size:
255
- print() # перевод строки после прогресс-бара
256
- return file_path