frp-tunnel 1.0.4__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.
frp_tunnel/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """FRP Tunnel Package"""
2
+
3
+ __version__ = "1.0.0"
4
+ __author__ = "Your Name"
5
+ __email__ = "your.email@example.com"
6
+
7
+ from .cli import main
8
+
9
+ __all__ = ['main']
frp_tunnel/_version.py ADDED
@@ -0,0 +1 @@
1
+ version = "1.0.4"
frp_tunnel/cli.py ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ FRP Tunnel CLI - Easy SSH tunneling with FRP
4
+ """
5
+
6
+ import click
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.text import Text
10
+ from .core.installer import install_binaries
11
+ from .core.tunnel import TunnelManager
12
+ from .core.platform import detect_platform, is_colab
13
+ from .core.config import ConfigManager
14
+
15
+ console = Console()
16
+ tunnel_manager = TunnelManager()
17
+ config_manager = ConfigManager()
18
+
19
+ @click.group()
20
+ @click.version_option()
21
+ def cli():
22
+ """🚀 FRP Tunnel - Easy SSH tunneling with FRP"""
23
+ pass
24
+
25
+ @cli.command()
26
+ @click.option('--mode', type=click.Choice(['server', 'client', 'colab', 'auto']), default='auto', help='Setup mode')
27
+ @click.option('--server', help='Server address (for client mode)')
28
+ @click.option('--token', help='Authentication token')
29
+ @click.option('--port', default=6001, help='Remote port')
30
+ @click.option('--user', default='colab', help='SSH username')
31
+ def setup(mode, server, token, port, user):
32
+ """Interactive setup wizard"""
33
+ console.print(Panel.fit("🚀 FRP Tunnel Setup", style="bold blue"))
34
+
35
+ # Auto-detect mode if not specified
36
+ if mode == 'auto':
37
+ if is_colab():
38
+ mode = 'colab'
39
+ console.print("🔬 Detected Google Colab environment")
40
+ else:
41
+ mode = click.prompt('Setup mode', type=click.Choice(['server', 'client']))
42
+
43
+ if mode == 'server':
44
+ setup_server()
45
+ elif mode in ['client', 'colab']:
46
+ setup_client(mode, server, token, port, user)
47
+
48
+ def setup_server():
49
+ """Setup FRP server"""
50
+ console.print("🖥️ Setting up FRP server...")
51
+
52
+ port = click.prompt('Server port', default=7000, type=int)
53
+ token = click.prompt('Authentication token (empty to generate)', default='', show_default=False)
54
+
55
+ if not token:
56
+ import secrets
57
+ token = f"frp_{secrets.token_hex(16)}"
58
+ console.print(f"🔑 Generated token: [bold yellow]{token}[/bold yellow]")
59
+
60
+ # Install server binary
61
+ with console.status("📦 Installing FRP server..."):
62
+ install_binaries('server')
63
+
64
+ # Create configuration
65
+ config = {
66
+ 'bind_port': port,
67
+ 'token': token
68
+ }
69
+ config_manager.create_server_config(config)
70
+
71
+ # Start server
72
+ if click.confirm('Start server now?', default=True):
73
+ tunnel_manager.start_server(config)
74
+ console.print("✅ Server started successfully!")
75
+ console.print(f"🔑 Share this token with clients: [bold]{token}[/bold]")
76
+
77
+ def setup_client(mode, server, token, port, user):
78
+ """Setup FRP client"""
79
+ console.print(f"📱 Setting up FRP client ({mode})...")
80
+
81
+ # Get configuration
82
+ if not server:
83
+ server = click.prompt('Server address')
84
+ if not token:
85
+ token = click.prompt('Authentication token')
86
+
87
+ # Install client binary
88
+ with console.status("📦 Installing FRP client..."):
89
+ install_binaries('client')
90
+
91
+ # Create configuration
92
+ config = {
93
+ 'server_addr': server,
94
+ 'server_port': 7000,
95
+ 'token': token,
96
+ 'remote_port': port,
97
+ 'username': user
98
+ }
99
+ config_manager.create_client_config(config)
100
+
101
+ # Setup SSH for Colab
102
+ if mode == 'colab':
103
+ with console.status("🔧 Setting up SSH..."):
104
+ tunnel_manager.setup_colab_ssh(user)
105
+
106
+ # Start client
107
+ tunnel_manager.start_client(config)
108
+ console.print("✅ Client connected successfully!")
109
+ console.print(f"🔗 SSH command: [bold]ssh -p {port} {user}@{server}[/bold]")
110
+
111
+ @cli.command()
112
+ @click.option('--server', required=True, help='Server address')
113
+ @click.option('--token', required=True, help='Authentication token')
114
+ @click.option('--port', default=6001, help='Remote port')
115
+ @click.option('--user', default='colab', help='SSH username')
116
+ def colab(server, token, port, user):
117
+ """Quick setup for Google Colab"""
118
+ console.print("🔬 Setting up Google Colab tunnel...")
119
+ setup_client('colab', server, token, port, user)
120
+
121
+ @cli.command()
122
+ def status():
123
+ """Show tunnel status"""
124
+ console.print("📊 Tunnel Status")
125
+ status_info = tunnel_manager.get_status()
126
+
127
+ if status_info['server_running']:
128
+ console.print("🖥️ Server: [green]Running[/green]")
129
+ else:
130
+ console.print("🖥️ Server: [red]Stopped[/red]")
131
+
132
+ if status_info['client_running']:
133
+ console.print("📱 Client: [green]Connected[/green]")
134
+ else:
135
+ console.print("📱 Client: [red]Disconnected[/red]")
136
+
137
+ @cli.command()
138
+ def stop():
139
+ """Stop all tunnels"""
140
+ console.print("🛑 Stopping tunnels...")
141
+ tunnel_manager.stop_all()
142
+ console.print("✅ All tunnels stopped")
143
+
144
+ @cli.command()
145
+ def logs():
146
+ """View tunnel logs"""
147
+ console.print("📝 Recent logs:")
148
+ logs = tunnel_manager.get_logs()
149
+ for log_line in logs:
150
+ console.print(log_line)
151
+
152
+ @cli.command()
153
+ def install():
154
+ """Install/update FRP binaries"""
155
+ console.print("📦 Installing FRP binaries...")
156
+ with console.status("Downloading..."):
157
+ install_binaries()
158
+ console.print("✅ Installation complete")
159
+
160
+ @cli.command()
161
+ def clean():
162
+ """Clean cache and temporary files"""
163
+ console.print("🧹 Cleaning cache...")
164
+ tunnel_manager.clean_cache()
165
+ console.print("✅ Cache cleaned")
166
+
167
+ def main():
168
+ """Entry point for the CLI"""
169
+ cli()
170
+
171
+ if __name__ == '__main__':
172
+ main()
@@ -0,0 +1,16 @@
1
+ """Core module initialization"""
2
+
3
+ from .platform import detect_platform, is_colab
4
+ from .installer import install_binaries, get_binary_path, is_installed
5
+ from .config import ConfigManager
6
+ from .tunnel import TunnelManager
7
+
8
+ __all__ = [
9
+ 'detect_platform',
10
+ 'is_colab',
11
+ 'install_binaries',
12
+ 'get_binary_path',
13
+ 'is_installed',
14
+ 'ConfigManager',
15
+ 'TunnelManager'
16
+ ]
@@ -0,0 +1,98 @@
1
+ """Configuration management"""
2
+
3
+ import os
4
+ import secrets
5
+ from pathlib import Path
6
+ from typing import Dict, Any
7
+ import configparser
8
+
9
+ class ConfigManager:
10
+ def __init__(self):
11
+ self.config_dir = Path.home() / '.frp-tunnel'
12
+ self.config_dir.mkdir(exist_ok=True)
13
+
14
+ self.server_config_path = self.config_dir / 'frps.ini'
15
+ self.client_config_path = self.config_dir / 'frpc.ini'
16
+
17
+ def create_server_config(self, config: Dict[str, Any]) -> Path:
18
+ """Create server configuration file"""
19
+ config_content = f"""[common]
20
+ bind_port = {config.get('bind_port', 7000)}
21
+ token = {config.get('token', self._generate_token())}
22
+
23
+ # Dashboard (optional)
24
+ dashboard_port = 7500
25
+ dashboard_user = admin
26
+ dashboard_pwd = admin
27
+
28
+ # Logging
29
+ log_file = {self.config_dir}/frps.log
30
+ log_level = info
31
+ log_max_days = 3
32
+
33
+ # Security
34
+ authentication_method = token
35
+ heartbeat_timeout = 90
36
+ """
37
+
38
+ with open(self.server_config_path, 'w') as f:
39
+ f.write(config_content)
40
+
41
+ return self.server_config_path
42
+
43
+ def create_client_config(self, config: Dict[str, Any]) -> Path:
44
+ """Create client configuration file"""
45
+ config_content = f"""[common]
46
+ server_addr = {config['server_addr']}
47
+ server_port = {config.get('server_port', 7000)}
48
+ token = {config['token']}
49
+
50
+ # Logging
51
+ log_file = {self.config_dir}/frpc.log
52
+ log_level = info
53
+
54
+ [ssh_{config.get('username', 'colab')}]
55
+ type = tcp
56
+ local_ip = 127.0.0.1
57
+ local_port = 22
58
+ remote_port = {config.get('remote_port', 6001)}
59
+ """
60
+
61
+ with open(self.client_config_path, 'w') as f:
62
+ f.write(config_content)
63
+
64
+ return self.client_config_path
65
+
66
+ def get_server_config(self) -> Dict[str, Any]:
67
+ """Read server configuration"""
68
+ if not self.server_config_path.exists():
69
+ return {}
70
+
71
+ config = configparser.ConfigParser()
72
+ config.read(self.server_config_path)
73
+
74
+ if 'common' not in config:
75
+ return {}
76
+
77
+ return dict(config['common'])
78
+
79
+ def get_client_config(self) -> Dict[str, Any]:
80
+ """Read client configuration"""
81
+ if not self.client_config_path.exists():
82
+ return {}
83
+
84
+ config = configparser.ConfigParser()
85
+ config.read(self.client_config_path)
86
+
87
+ if 'common' not in config:
88
+ return {}
89
+
90
+ return dict(config['common'])
91
+
92
+ def _generate_token(self) -> str:
93
+ """Generate a secure token"""
94
+ return f"frp_{secrets.token_hex(16)}"
95
+
96
+ def get_log_path(self, component: str) -> Path:
97
+ """Get log file path"""
98
+ return self.config_dir / f"frp{component[0]}.log"
@@ -0,0 +1,118 @@
1
+ """Binary installer for FRP"""
2
+
3
+ import os
4
+ import tarfile
5
+ import tempfile
6
+ import urllib.request
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from .platform import get_frp_binary_url, get_binary_names, detect_platform
11
+
12
+ class BinaryInstaller:
13
+ def __init__(self):
14
+ self.platform_info = detect_platform()
15
+ self.binary_names = get_binary_names()
16
+ self.install_dir = Path.home() / '.frp-tunnel' / 'bin'
17
+ self.install_dir.mkdir(parents=True, exist_ok=True)
18
+
19
+ def install_binaries(self, component: Optional[str] = None) -> bool:
20
+ """Install FRP binaries
21
+
22
+ Args:
23
+ component: 'server', 'client', or None for both
24
+ """
25
+ try:
26
+ # Download and extract
27
+ binary_path = self._download_and_extract()
28
+
29
+ # Copy required binaries
30
+ if component == 'server' or component is None:
31
+ self._copy_binary(binary_path, 'server')
32
+
33
+ if component == 'client' or component is None:
34
+ self._copy_binary(binary_path, 'client')
35
+
36
+ # Make binaries executable
37
+ self._make_executable()
38
+
39
+ return True
40
+
41
+ except Exception as e:
42
+ print(f"Error installing binaries: {e}")
43
+ return False
44
+
45
+ def _download_and_extract(self) -> Path:
46
+ """Download and extract FRP archive"""
47
+ url = get_frp_binary_url()
48
+
49
+ with tempfile.TemporaryDirectory() as temp_dir:
50
+ temp_path = Path(temp_dir)
51
+ archive_path = temp_path / 'frp.tar.gz'
52
+
53
+ # Download
54
+ urllib.request.urlretrieve(url, archive_path)
55
+
56
+ # Extract
57
+ with tarfile.open(archive_path, 'r:gz') as tar:
58
+ tar.extractall(temp_path)
59
+
60
+ # Find extracted directory
61
+ extracted_dirs = [d for d in temp_path.iterdir() if d.is_dir()]
62
+ if not extracted_dirs:
63
+ raise Exception("No directory found in archive")
64
+
65
+ binary_path = extracted_dirs[0]
66
+
67
+ # Copy to permanent location
68
+ import shutil
69
+ permanent_path = self.install_dir / 'extracted'
70
+ if permanent_path.exists():
71
+ shutil.rmtree(permanent_path)
72
+ shutil.copytree(binary_path, permanent_path)
73
+
74
+ return permanent_path
75
+
76
+ def _copy_binary(self, source_dir: Path, component: str):
77
+ """Copy binary to install directory"""
78
+ binary_name = self.binary_names[component]
79
+ source_file = source_dir / binary_name
80
+ dest_file = self.install_dir / binary_name
81
+
82
+ if not source_file.exists():
83
+ raise Exception(f"Binary {binary_name} not found in archive")
84
+
85
+ import shutil
86
+ shutil.copy2(source_file, dest_file)
87
+
88
+ def _make_executable(self):
89
+ """Make binaries executable on Unix systems"""
90
+ if self.platform_info['os'] != 'windows':
91
+ for binary_name in self.binary_names.values():
92
+ binary_path = self.install_dir / binary_name
93
+ if binary_path.exists():
94
+ os.chmod(binary_path, 0o755)
95
+
96
+ def get_binary_path(self, component: str) -> Path:
97
+ """Get path to installed binary"""
98
+ binary_name = self.binary_names[component]
99
+ return self.install_dir / binary_name
100
+
101
+ def is_installed(self, component: str) -> bool:
102
+ """Check if binary is installed"""
103
+ return self.get_binary_path(component).exists()
104
+
105
+ # Global installer instance
106
+ _installer = BinaryInstaller()
107
+
108
+ def install_binaries(component: Optional[str] = None) -> bool:
109
+ """Install FRP binaries"""
110
+ return _installer.install_binaries(component)
111
+
112
+ def get_binary_path(component: str) -> Path:
113
+ """Get path to binary"""
114
+ return _installer.get_binary_path(component)
115
+
116
+ def is_installed(component: str) -> bool:
117
+ """Check if binary is installed"""
118
+ return _installer.is_installed(component)
@@ -0,0 +1,92 @@
1
+ """Platform detection and utilities"""
2
+
3
+ import os
4
+ import platform
5
+ import subprocess
6
+ from typing import Dict, List
7
+
8
+ def detect_platform() -> Dict[str, str]:
9
+ """Detect current platform and architecture"""
10
+ system = platform.system().lower()
11
+ machine = platform.machine().lower()
12
+
13
+ # Normalize architecture names
14
+ arch_map = {
15
+ 'x86_64': 'amd64',
16
+ 'amd64': 'amd64',
17
+ 'aarch64': 'arm64',
18
+ 'arm64': 'arm64',
19
+ 'armv7l': 'arm',
20
+ }
21
+
22
+ arch = arch_map.get(machine, machine)
23
+
24
+ return {
25
+ 'os': system,
26
+ 'arch': arch,
27
+ 'platform': f"{system}-{arch}"
28
+ }
29
+
30
+ def is_colab() -> bool:
31
+ """Check if running in Google Colab"""
32
+ return 'COLAB_GPU' in os.environ or os.path.exists('/content')
33
+
34
+ def is_docker() -> bool:
35
+ """Check if running in Docker"""
36
+ return os.path.exists('/.dockerenv')
37
+
38
+ def check_requirements() -> Dict[str, bool]:
39
+ """Check if required tools are available"""
40
+ requirements = {
41
+ 'ssh': _command_exists('ssh'),
42
+ 'wget': _command_exists('wget') or _command_exists('curl'),
43
+ 'tar': _command_exists('tar'),
44
+ }
45
+
46
+ # Additional checks for different platforms
47
+ if platform.system().lower() == 'linux':
48
+ requirements['systemctl'] = _command_exists('systemctl')
49
+
50
+ return requirements
51
+
52
+ def _command_exists(command: str) -> bool:
53
+ """Check if a command exists in PATH"""
54
+ try:
55
+ subprocess.run(['which', command],
56
+ capture_output=True,
57
+ check=True)
58
+ return True
59
+ except (subprocess.CalledProcessError, FileNotFoundError):
60
+ return False
61
+
62
+ def get_frp_binary_url(version: str = "0.52.3") -> str:
63
+ """Get FRP binary download URL for current platform"""
64
+ platform_info = detect_platform()
65
+
66
+ # Map platform names to FRP naming convention
67
+ os_map = {
68
+ 'linux': 'linux',
69
+ 'darwin': 'darwin',
70
+ 'windows': 'windows'
71
+ }
72
+
73
+ os_name = os_map.get(platform_info['os'], platform_info['os'])
74
+ arch = platform_info['arch']
75
+
76
+ filename = f"frp_{version}_{os_name}_{arch}.tar.gz"
77
+ url = f"https://github.com/fatedier/frp/releases/download/v{version}/{filename}"
78
+
79
+ return url
80
+
81
+ def get_binary_names() -> Dict[str, str]:
82
+ """Get binary names for current platform"""
83
+ if platform.system().lower() == 'windows':
84
+ return {
85
+ 'server': 'frps.exe',
86
+ 'client': 'frpc.exe'
87
+ }
88
+ else:
89
+ return {
90
+ 'server': 'frps',
91
+ 'client': 'frpc'
92
+ }
@@ -0,0 +1,208 @@
1
+ """Tunnel management"""
2
+
3
+ import os
4
+ import subprocess
5
+ import signal
6
+ import time
7
+ from pathlib import Path
8
+ from typing import Dict, List, Optional
9
+
10
+ from .installer import get_binary_path, is_installed, install_binaries
11
+ from .config import ConfigManager
12
+ from .platform import is_colab
13
+
14
+ class TunnelManager:
15
+ def __init__(self):
16
+ self.config_manager = ConfigManager()
17
+ self.pid_dir = Path.home() / '.frp-tunnel' / 'pids'
18
+ self.pid_dir.mkdir(parents=True, exist_ok=True)
19
+
20
+ def start_server(self, config: Dict) -> bool:
21
+ """Start FRP server"""
22
+ if not is_installed('server'):
23
+ install_binaries('server')
24
+
25
+ binary_path = get_binary_path('server')
26
+ config_path = self.config_manager.create_server_config(config)
27
+
28
+ try:
29
+ # Start server process
30
+ process = subprocess.Popen([
31
+ str(binary_path),
32
+ '-c', str(config_path)
33
+ ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
34
+
35
+ # Save PID
36
+ pid_file = self.pid_dir / 'frps.pid'
37
+ with open(pid_file, 'w') as f:
38
+ f.write(str(process.pid))
39
+
40
+ # Wait a moment to check if it started successfully
41
+ time.sleep(2)
42
+ if process.poll() is None:
43
+ return True
44
+ else:
45
+ return False
46
+
47
+ except Exception as e:
48
+ print(f"Error starting server: {e}")
49
+ return False
50
+
51
+ def start_client(self, config: Dict) -> bool:
52
+ """Start FRP client"""
53
+ if not is_installed('client'):
54
+ install_binaries('client')
55
+
56
+ binary_path = get_binary_path('client')
57
+ config_path = self.config_manager.create_client_config(config)
58
+
59
+ try:
60
+ # Start client process
61
+ process = subprocess.Popen([
62
+ str(binary_path),
63
+ '-c', str(config_path)
64
+ ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
65
+
66
+ # Save PID
67
+ pid_file = self.pid_dir / 'frpc.pid'
68
+ with open(pid_file, 'w') as f:
69
+ f.write(str(process.pid))
70
+
71
+ # Wait a moment to check if it started successfully
72
+ time.sleep(2)
73
+ if process.poll() is None:
74
+ return True
75
+ else:
76
+ return False
77
+
78
+ except Exception as e:
79
+ print(f"Error starting client: {e}")
80
+ return False
81
+
82
+ def setup_colab_ssh(self, username: str = 'colab'):
83
+ """Setup SSH for Google Colab environment"""
84
+ try:
85
+ # Generate SSH key if not exists
86
+ subprocess.run([
87
+ 'bash', '-c',
88
+ 'test -f ~/.ssh/id_rsa || ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""'
89
+ ], check=True)
90
+
91
+ # Create user if not exists
92
+ subprocess.run([
93
+ 'bash', '-c',
94
+ f'id {username} || sudo useradd -m -s /bin/bash {username}'
95
+ ], check=False) # Don't fail if user exists
96
+
97
+ # Setup SSH key
98
+ subprocess.run([
99
+ 'bash', '-c',
100
+ f'''
101
+ sudo mkdir -p /home/{username}/.ssh
102
+ sudo cp ~/.ssh/id_rsa.pub /home/{username}/.ssh/authorized_keys
103
+ sudo chown -R {username}:{username} /home/{username}/.ssh
104
+ sudo chmod 700 /home/{username}/.ssh
105
+ sudo chmod 600 /home/{username}/.ssh/authorized_keys
106
+ '''
107
+ ], check=False)
108
+
109
+ # Start SSH service
110
+ subprocess.run([
111
+ 'bash', '-c',
112
+ 'sudo systemctl start ssh || sudo service ssh start'
113
+ ], check=False)
114
+
115
+ except Exception as e:
116
+ print(f"Warning: Some SSH setup steps failed: {e}")
117
+
118
+ def stop_process(self, component: str) -> bool:
119
+ """Stop FRP process"""
120
+ pid_file = self.pid_dir / f'frp{component[0]}.pid'
121
+
122
+ if not pid_file.exists():
123
+ return True
124
+
125
+ try:
126
+ with open(pid_file, 'r') as f:
127
+ pid = int(f.read().strip())
128
+
129
+ # Try to terminate gracefully
130
+ os.kill(pid, signal.SIGTERM)
131
+ time.sleep(2)
132
+
133
+ # Force kill if still running
134
+ try:
135
+ os.kill(pid, signal.SIGKILL)
136
+ except ProcessLookupError:
137
+ pass # Process already dead
138
+
139
+ # Remove PID file
140
+ pid_file.unlink()
141
+ return True
142
+
143
+ except Exception as e:
144
+ print(f"Error stopping {component}: {e}")
145
+ return False
146
+
147
+ def stop_all(self):
148
+ """Stop all FRP processes"""
149
+ self.stop_process('server')
150
+ self.stop_process('client')
151
+
152
+ def get_status(self) -> Dict[str, bool]:
153
+ """Get status of FRP processes"""
154
+ return {
155
+ 'server_running': self._is_process_running('server'),
156
+ 'client_running': self._is_process_running('client')
157
+ }
158
+
159
+ def _is_process_running(self, component: str) -> bool:
160
+ """Check if process is running"""
161
+ pid_file = self.pid_dir / f'frp{component[0]}.pid'
162
+
163
+ if not pid_file.exists():
164
+ return False
165
+
166
+ try:
167
+ with open(pid_file, 'r') as f:
168
+ pid = int(f.read().strip())
169
+
170
+ # Check if process exists
171
+ os.kill(pid, 0)
172
+ return True
173
+
174
+ except (ProcessLookupError, ValueError, OSError):
175
+ # Remove stale PID file
176
+ if pid_file.exists():
177
+ pid_file.unlink()
178
+ return False
179
+
180
+ def get_logs(self, lines: int = 20) -> List[str]:
181
+ """Get recent log lines"""
182
+ logs = []
183
+
184
+ for component in ['server', 'client']:
185
+ log_path = self.config_manager.get_log_path(component)
186
+ if log_path.exists():
187
+ try:
188
+ with open(log_path, 'r') as f:
189
+ log_lines = f.readlines()
190
+ recent_lines = log_lines[-lines:]
191
+ logs.extend([f"[{component}] {line.strip()}" for line in recent_lines])
192
+ except Exception:
193
+ pass
194
+
195
+ return logs
196
+
197
+ def clean_cache(self):
198
+ """Clean cache and temporary files"""
199
+ import shutil
200
+
201
+ # Clean extracted binaries
202
+ extracted_path = Path.home() / '.frp-tunnel' / 'bin' / 'extracted'
203
+ if extracted_path.exists():
204
+ shutil.rmtree(extracted_path)
205
+
206
+ # Clean old log files
207
+ for log_file in self.config_manager.config_dir.glob('*.log.*'):
208
+ log_file.unlink()
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: frp-tunnel
3
+ Version: 1.0.4
4
+ Summary: Easy SSH tunneling with FRP - One command setup for Google Colab and remote servers
5
+ Author-email: Your Name <your.email@example.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/cicy-dev/frp-tunnel
8
+ Project-URL: Repository, https://github.com/cicy-dev/frp-tunnel.git
9
+ Project-URL: Issues, https://github.com/cicy-dev/frp-tunnel/issues
10
+ Project-URL: Documentation, https://github.com/cicy-dev/frp-tunnel/docs
11
+ Keywords: frp,ssh,tunnel,proxy,colab,remote,development
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Topic :: Internet :: Proxy Servers
22
+ Classifier: Topic :: System :: Networking
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.7
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: click>=8.0.0
28
+ Requires-Dist: rich>=12.0.0
29
+ Requires-Dist: requests>=2.25.0
30
+ Requires-Dist: pyyaml>=5.4.0
31
+ Requires-Dist: psutil>=5.8.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=6.0; extra == "dev"
34
+ Requires-Dist: pytest-cov>=2.0; extra == "dev"
35
+ Requires-Dist: black>=21.0; extra == "dev"
36
+ Requires-Dist: flake8>=3.8; extra == "dev"
37
+ Requires-Dist: mypy>=0.800; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # 🚀 FRP Tunnel - SSH Access Made Easy
41
+
42
+ **[中文文档](README_CN.md) | [English](README.md)**
43
+
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
45
+ [![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20Windows%20%7C%20macOS-blue.svg)](https://github.com/cicy-dev/frp-tunnel)
46
+
47
+ > **Connect to Google Colab or any remote server via SSH in 30 seconds. No complex setup needed!**
48
+
49
+ ## 🎯 What This Does
50
+
51
+ - **Problem**: Can't SSH into Google Colab or access remote servers behind firewalls
52
+ - **Solution**: Creates a secure tunnel so you can SSH from anywhere
53
+ - **Result**: Use your favorite tools (VS Code, file transfer, etc.) with remote servers
54
+
55
+ ## 🏗️ How It Works
56
+
57
+ ```
58
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
59
+ │ Local Client │ │ GCP Server │ │ Google Colab │
60
+ │ (Any Platform) │ │ (frps:7000) │ │ (frpc+SSH) │
61
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
62
+ │ │ │
63
+ │ SSH -p 6001-6010 │ │
64
+ └──────────────────────┼───────────────────────┘
65
+
66
+ FRP Tunnel Forwarding
67
+ 6001-6010 → Target:22
68
+ ```
69
+
70
+ ## ⚡ Quick Start (3 Steps)
71
+
72
+ ### Step 1: Install
73
+ ```bash
74
+ pip install frp-tunnel
75
+ ```
76
+
77
+ ### Step 2: Set Up Server (One-time)
78
+ ```bash
79
+ # On your VPS/cloud server
80
+ frp-tunnel setup
81
+ ```
82
+ *Follow the prompts - it takes 30 seconds*
83
+
84
+ ### Step 3: Connect from Anywhere
85
+ ```bash
86
+ # Google Colab (paste in notebook)
87
+ !pip install frp-tunnel && frp-tunnel colab --server YOUR_SERVER_IP --token YOUR_TOKEN
88
+
89
+ # Your computer
90
+ frp-tunnel client --server YOUR_SERVER_IP --token YOUR_TOKEN
91
+
92
+ # Then SSH normally
93
+ ssh -p 6001 colab@YOUR_SERVER_IP
94
+ ```
95
+
96
+ ## 🔧 Real-World Examples
97
+
98
+ ### Example 1: Access Google Colab Files
99
+ ```python
100
+ # In Colab notebook
101
+ !pip install frp-tunnel && frp-tunnel colab --server 34.123.45.67 --token abc123
102
+ ```
103
+ ```bash
104
+ # On your computer
105
+ ssh -p 6001 colab@34.123.45.67
106
+ # Now you can browse files, upload/download, use git, etc.
107
+ ```
108
+
109
+ ### Example 2: VS Code Remote Development
110
+ 1. Set up tunnel (steps above)
111
+ 2. In VS Code: Install "Remote-SSH" extension
112
+ 3. Connect to `colab@YOUR_SERVER_IP:6001`
113
+ 4. Code directly in Colab with full VS Code features!
114
+
115
+ ### Example 3: Multiple Connections
116
+ ```bash
117
+ # Colab 1
118
+ frp-tunnel colab --server YOUR_IP --token YOUR_TOKEN --port 6001
119
+
120
+ # Colab 2
121
+ frp-tunnel colab --server YOUR_IP --token YOUR_TOKEN --port 6002
122
+
123
+ # Your laptop
124
+ frp-tunnel client --server YOUR_IP --token YOUR_TOKEN --port 6003
125
+ ```
126
+
127
+ ## 🛠️ Troubleshooting (Common Issues)
128
+
129
+ ### "Connection refused"
130
+ ```bash
131
+ # Check if server is running
132
+ ssh YOUR_SERVER_IP "ps aux | grep frps"
133
+ ```
134
+
135
+ ### "Permission denied"
136
+ ```bash
137
+ # Make sure you're using the right port
138
+ ssh -p 6001 colab@YOUR_SERVER_IP # Not port 22!
139
+ ```
140
+
141
+ ### "Token mismatch"
142
+ ```bash
143
+ # Get the token from your server
144
+ ssh YOUR_SERVER_IP "cat ~/data/frp/frps.ini | grep token"
145
+ ```
146
+
147
+ ## 📋 What You Need
148
+
149
+ - **Server**: Any Linux VPS (Google Cloud, AWS, DigitalOcean, etc.)
150
+ - **Ports**: Open ports 6001-6010 and 7000 on your server
151
+ - **Client**: Any computer with SSH (Windows/Mac/Linux)
152
+
153
+ ### Quick Server Setup (GCP/AWS)
154
+ ```bash
155
+ # Open firewall ports
156
+ gcloud compute firewall-rules create frp-tunnel --allow tcp:6001-6010,tcp:7000
157
+
158
+ # Or for AWS
159
+ aws ec2 authorize-security-group-ingress --group-id sg-xxxxx --protocol tcp --port 6001-6010 --cidr 0.0.0.0/0
160
+ ```
161
+
162
+ ## 🎉 That's It!
163
+
164
+ No complex configuration files, no networking knowledge needed. Just install, run, and connect!
165
+
166
+ **Need help?** [Open an issue](https://github.com/cicy-dev/frp-tunnel/issues) - we respond quickly!
167
+
168
+ ---
169
+
170
+ ⭐ **Star this repo if it saved you time!**
@@ -0,0 +1,14 @@
1
+ frp_tunnel/__init__.py,sha256=xef6hdo6PQOVvOmCnQqJJ708jGKFZ3wtioy6pQNX3Bk,153
2
+ frp_tunnel/_version.py,sha256=R604FHUDDjGzi5hem3DYdlBvcBVjznL7PmfCQFtRkDI,18
3
+ frp_tunnel/cli.py,sha256=73AgYgzK1A3tKiSS78zs_FjDXlEkZctP8uecmszrCms,5389
4
+ frp_tunnel/core/__init__.py,sha256=4KMZCKElGqspMj3rgyKK04hZJ1f-DCrhk0IzLOGRLfI,384
5
+ frp_tunnel/core/config.py,sha256=25EdLN0lKpZlnPjd3umDtKturpkaIZxFFwlAcORZV1Y,2713
6
+ frp_tunnel/core/installer.py,sha256=53CKUUB0ExCF2h_zBW17QRw0xe8dhY9prQ1S2ZqaD6c,4098
7
+ frp_tunnel/core/platform.py,sha256=1E4knN-lKUp97kArbCr0jumU3l34lzqVTNvciFLvanw,2569
8
+ frp_tunnel/core/tunnel.py,sha256=rZtCFs3KsOZSO1M1Nl2TnTLgHGZOF-T_0iqBHXvecR4,6930
9
+ frp_tunnel-1.0.4.dist-info/licenses/LICENSE,sha256=olOm-Mwd2NnXAzpefs0sowX0uPN7gio-g1KxpEv6Lvs,1079
10
+ frp_tunnel-1.0.4.dist-info/METADATA,sha256=JkH_7T42Z6XWf7nhPTXnSDjFSwTxiMTOerhWkh9RSIY,5787
11
+ frp_tunnel-1.0.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
+ frp_tunnel-1.0.4.dist-info/entry_points.txt,sha256=zJ-DXLnJyhjkPC-sLMPLgxm53seoVFCqMo7kDXiYHig,77
13
+ frp_tunnel-1.0.4.dist-info/top_level.txt,sha256=KwiOTKTjTUQkJbD0Ae6Ym9HRvNjKwVo0BO4YNQf2Vzc,11
14
+ frp_tunnel-1.0.4.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ frp = frp_tunnel.cli:main
3
+ frp-tunnel = frp_tunnel.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FRP SSH Tunnel Project
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ frp_tunnel