pia-proxy 1.0.0__tar.gz
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.
- pia_proxy-1.0.0/LICENSE +21 -0
- pia_proxy-1.0.0/MANIFEST.in +28 -0
- pia_proxy-1.0.0/PKG-INFO +124 -0
- pia_proxy-1.0.0/README.md +97 -0
- pia_proxy-1.0.0/cli/__init__.py +5 -0
- pia_proxy-1.0.0/cli/api_client.py +128 -0
- pia_proxy-1.0.0/cli/commands/__init__.py +3 -0
- pia_proxy-1.0.0/cli/commands/config.py +96 -0
- pia_proxy-1.0.0/cli/commands/doctor.py +106 -0
- pia_proxy-1.0.0/cli/commands/export.py +53 -0
- pia_proxy-1.0.0/cli/commands/logs.py +33 -0
- pia_proxy-1.0.0/cli/commands/proxy.py +63 -0
- pia_proxy-1.0.0/cli/commands/rotate.py +90 -0
- pia_proxy-1.0.0/cli/commands/rotate_time.py +71 -0
- pia_proxy-1.0.0/cli/commands/scale.py +56 -0
- pia_proxy-1.0.0/cli/commands/service.py +136 -0
- pia_proxy-1.0.0/cli/commands/setup.py +65 -0
- pia_proxy-1.0.0/cli/commands/status.py +182 -0
- pia_proxy-1.0.0/cli/commands/test.py +126 -0
- pia_proxy-1.0.0/cli/commands/workers.py +66 -0
- pia_proxy-1.0.0/cli/config.py +207 -0
- pia_proxy-1.0.0/cli/docker_ctl.py +178 -0
- pia_proxy-1.0.0/cli/main.py +70 -0
- pia_proxy-1.0.0/cli/templates/.env.example +130 -0
- pia_proxy-1.0.0/cli/templates/docker-compose.yml +575 -0
- pia_proxy-1.0.0/pia_proxy.egg-info/PKG-INFO +124 -0
- pia_proxy-1.0.0/pia_proxy.egg-info/SOURCES.txt +31 -0
- pia_proxy-1.0.0/pia_proxy.egg-info/dependency_links.txt +1 -0
- pia_proxy-1.0.0/pia_proxy.egg-info/entry_points.txt +3 -0
- pia_proxy-1.0.0/pia_proxy.egg-info/requires.txt +2 -0
- pia_proxy-1.0.0/pia_proxy.egg-info/top_level.txt +1 -0
- pia_proxy-1.0.0/pyproject.toml +55 -0
- pia_proxy-1.0.0/setup.cfg +4 -0
pia_proxy-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 zSOCKS Team
|
|
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,28 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include LICENSE
|
|
3
|
+
include pyproject.toml
|
|
4
|
+
recursive-include cli/templates *
|
|
5
|
+
|
|
6
|
+
# Exclude sensitive secrets and runtime files
|
|
7
|
+
prune credentials
|
|
8
|
+
prune prod/credentials
|
|
9
|
+
prune prod
|
|
10
|
+
prune app
|
|
11
|
+
prune gateway
|
|
12
|
+
prune installers
|
|
13
|
+
global-exclude *.pyc
|
|
14
|
+
global-exclude __pycache__
|
|
15
|
+
global-exclude .env
|
|
16
|
+
global-exclude .env*
|
|
17
|
+
include cli/templates/.env.example
|
|
18
|
+
global-exclude *.json
|
|
19
|
+
global-exclude *.log
|
|
20
|
+
global-exclude *.sh
|
|
21
|
+
global-exclude check_*.py
|
|
22
|
+
global-exclude monitor_*.py
|
|
23
|
+
global-exclude launch_*.py
|
|
24
|
+
global-exclude run_*.py
|
|
25
|
+
global-exclude verify_*.py
|
|
26
|
+
global-exclude deploy_*.py
|
|
27
|
+
global-exclude connect_*.py
|
|
28
|
+
global-exclude debug_*.py
|
pia_proxy-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pia-proxy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: High-Performance PIA SOCKS5 Multi-Worker Proxy Gateway CLI
|
|
5
|
+
Author: zSOCKS Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/videoveo3pro-debug/pia
|
|
8
|
+
Project-URL: Repository, https://github.com/videoveo3pro-debug/pia.git
|
|
9
|
+
Keywords: proxy,socks5,pia,vpn,vpn-gateway,rotate-ip,proxy-pool,haproxy
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: click>=8.1.0
|
|
25
|
+
Requires-Dist: rich>=13.0.0
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# PIA SOCKS5 Multi-Worker Proxy Gateway & CLI
|
|
29
|
+
|
|
30
|
+
Hệ thống Proxy SOCKS5 tập trung hiệu năng cao, điều phối **32 Worker VPN Private Internet Access (PIA)** hoạt động song song trong các container biệt lập, đảm bảo **luôn có ít nhất 20 IP công khai độc lập hoạt động đồng thời**, hỗ trợ tự động xoay IP (Auto-Rotate) mượt mà với **100% Uptime (Zero-Downtime)**.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ⚡ Ứng Dụng Quản Lý CLI (`pia-proxy` / `zsocks`)
|
|
35
|
+
|
|
36
|
+
Toàn bộ hệ thống hiện đã được tích hợp bộ công cụ CLI hiện đại, trực quan giúp quản trị, vận hành, kiểm thử và mở rộng pool proxy chỉ bằng các câu lệnh đơn giản:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Cài đặt symlink CLI vào PATH (nếu chưa có):
|
|
40
|
+
chmod +x bin/pia-proxy
|
|
41
|
+
ln -sf $(pwd)/bin/pia-proxy ~/.local/bin/pia-proxy
|
|
42
|
+
ln -sf $(pwd)/bin/pia-proxy ~/.local/bin/zsocks
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Các lệnh CLI chính:
|
|
46
|
+
|
|
47
|
+
| Lệnh | Chức năng |
|
|
48
|
+
| :--- | :--- |
|
|
49
|
+
| `pia-proxy doctor` | Kiểm tra môi trường hệ thống: Docker, TUN device, Swap, RAM, Port |
|
|
50
|
+
| `pia-proxy config show` | Xem thông tin cấu hình, credentials, ports |
|
|
51
|
+
| `pia-proxy config set-credentials` | Nhập tài khoản PIA (Username / Password) |
|
|
52
|
+
| `pia-proxy config set-proxy-auth` | Đổi thông tin đăng nhập SOCKS5 (`user`, `pass`) |
|
|
53
|
+
| `pia-proxy start` | Khởi động hệ thống thông minh (Staggered Boot chống nghẽn CPU) |
|
|
54
|
+
| `pia-proxy stop` | Dừng an toàn toàn bộ hệ thống hoặc từng worker cụ thể |
|
|
55
|
+
| `pia-proxy restart [-W worker]` | Khởi động lại cụm proxy hoặc một worker chỉ định |
|
|
56
|
+
| `pia-proxy status [-w]` | Xem bảng trạng thái trực tiếp: Exit IP, Uptime, Worker READY, Unique IPs |
|
|
57
|
+
| `pia-proxy scale <N>` | **Mở rộng / thu gọn quy mô động** (ví dụ: `scale 20`, `scale 32`) Zero-Downtime |
|
|
58
|
+
| `pia-proxy rotate [ID] [--all]` | Kích hoạt xoay IP ngay lập tức cho 1 worker hoặc toàn bộ pool |
|
|
59
|
+
| `pia-proxy test [-n 5]` | Kiểm tra kết nối SOCKS5 gateway, đo độ trễ, đếm IP và xác nhận Zero-Leak |
|
|
60
|
+
| `pia-proxy export [-f format]` | **Xuất proxy định dạng sẵn** cho tool nuôi nick, Gologin, AdsPower, bot |
|
|
61
|
+
| `pia-proxy logs [-f]` | Theo dõi log real-time của worker hoặc gateway |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 🚀 Đặc Điểm Nổi Bật
|
|
66
|
+
|
|
67
|
+
- **Cổng SOCKS5 Duy Nhất Cho Mọi Ứng Dụng:**
|
|
68
|
+
`socks5h://proxy_user:proxy_password_secure123@<IP_SERVER>:1087`
|
|
69
|
+
- **Cân Bằng Tải Thông Minh (Round-Robin):**
|
|
70
|
+
Mỗi kết nối / request mới tự động chuyển hướng qua một Worker VPN khác nhau, mang lại dải IP đa dạng và phân bổ đều.
|
|
71
|
+
- **Quy Tắc Xoay Vòng IP (Strict Rotation Rules):**
|
|
72
|
+
- Chu kỳ tự động xoay: **36s – 69s**.
|
|
73
|
+
- Giới hạn cứng Uptime: **70s** (Bất kỳ IP nào đạt 70s sẽ bị ép rotate ngay lập tức).
|
|
74
|
+
- Khống chế dung lượng: Không bao giờ để số worker khả dụng tụt dưới **20 IP**.
|
|
75
|
+
- **Tối Ưu Phần Cứng Tuyệt Đối (Zero CPU Overhead):**
|
|
76
|
+
- Chạy mượt mà ngay cả trên VPS 1 vCPU / 1GB RAM.
|
|
77
|
+
- Sử dụng lightweight TCP healthchecks trên cổng 1080, loại bỏ hoàn toàn hiện tượng 503 flapping và CPU spikes.
|
|
78
|
+
- **Bảo Mật Zero-Leak:**
|
|
79
|
+
- Tường lửa iptables killswitch trong từng worker chặn 100% rò rỉ IP gốc của VPS.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 📖 Ví Dụ Sử Dụng Nhanh
|
|
84
|
+
|
|
85
|
+
### 1. Xuất chuỗi proxy dùng cho phần mềm:
|
|
86
|
+
```bash
|
|
87
|
+
# Định dạng chuẩn IP:PORT:USER:PASS (dùng cho antidetect browser, bot, tool)
|
|
88
|
+
pia-proxy export --format standard
|
|
89
|
+
# Kết quả: 14.169.55.237:1087:proxy_user:proxy_password_secure123
|
|
90
|
+
|
|
91
|
+
# Định dạng URL:
|
|
92
|
+
pia-proxy export --format url
|
|
93
|
+
# Kết quả: socks5h://proxy_user:proxy_password_secure123@14.169.55.237:1087
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 2. Xem bảng trạng thái trực quan:
|
|
97
|
+
```bash
|
|
98
|
+
pia-proxy status
|
|
99
|
+
# Giám sát trực tiếp liên tục mỗi 2 giây:
|
|
100
|
+
pia-proxy status --watch -i 2
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3. Mở rộng số lượng worker khi cần:
|
|
104
|
+
```bash
|
|
105
|
+
# Tăng lên 32 worker:
|
|
106
|
+
pia-proxy scale 32
|
|
107
|
+
|
|
108
|
+
# Giảm xuống 20 worker:
|
|
109
|
+
pia-proxy scale 20
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 4. Kiểm thử chất lượng proxy & Zero-leak:
|
|
113
|
+
```bash
|
|
114
|
+
pia-proxy test --count 5
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 🛠️ Triển Khai Thủ Công & Link Trực Tiếp
|
|
120
|
+
|
|
121
|
+
- **Web Dashboard:** `http://<IP_SERVER>:8007/`
|
|
122
|
+
- **API Status:** `http://<IP_SERVER>:8007/api/status`
|
|
123
|
+
- **HAProxy Live Stats:** `http://<IP_SERVER>:8411/`
|
|
124
|
+
- **Hướng dẫn cấu hình nâng cao:** [prod/README.md](prod/README.md)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# PIA SOCKS5 Multi-Worker Proxy Gateway & CLI
|
|
2
|
+
|
|
3
|
+
Hệ thống Proxy SOCKS5 tập trung hiệu năng cao, điều phối **32 Worker VPN Private Internet Access (PIA)** hoạt động song song trong các container biệt lập, đảm bảo **luôn có ít nhất 20 IP công khai độc lập hoạt động đồng thời**, hỗ trợ tự động xoay IP (Auto-Rotate) mượt mà với **100% Uptime (Zero-Downtime)**.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ⚡ Ứng Dụng Quản Lý CLI (`pia-proxy` / `zsocks`)
|
|
8
|
+
|
|
9
|
+
Toàn bộ hệ thống hiện đã được tích hợp bộ công cụ CLI hiện đại, trực quan giúp quản trị, vận hành, kiểm thử và mở rộng pool proxy chỉ bằng các câu lệnh đơn giản:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Cài đặt symlink CLI vào PATH (nếu chưa có):
|
|
13
|
+
chmod +x bin/pia-proxy
|
|
14
|
+
ln -sf $(pwd)/bin/pia-proxy ~/.local/bin/pia-proxy
|
|
15
|
+
ln -sf $(pwd)/bin/pia-proxy ~/.local/bin/zsocks
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Các lệnh CLI chính:
|
|
19
|
+
|
|
20
|
+
| Lệnh | Chức năng |
|
|
21
|
+
| :--- | :--- |
|
|
22
|
+
| `pia-proxy doctor` | Kiểm tra môi trường hệ thống: Docker, TUN device, Swap, RAM, Port |
|
|
23
|
+
| `pia-proxy config show` | Xem thông tin cấu hình, credentials, ports |
|
|
24
|
+
| `pia-proxy config set-credentials` | Nhập tài khoản PIA (Username / Password) |
|
|
25
|
+
| `pia-proxy config set-proxy-auth` | Đổi thông tin đăng nhập SOCKS5 (`user`, `pass`) |
|
|
26
|
+
| `pia-proxy start` | Khởi động hệ thống thông minh (Staggered Boot chống nghẽn CPU) |
|
|
27
|
+
| `pia-proxy stop` | Dừng an toàn toàn bộ hệ thống hoặc từng worker cụ thể |
|
|
28
|
+
| `pia-proxy restart [-W worker]` | Khởi động lại cụm proxy hoặc một worker chỉ định |
|
|
29
|
+
| `pia-proxy status [-w]` | Xem bảng trạng thái trực tiếp: Exit IP, Uptime, Worker READY, Unique IPs |
|
|
30
|
+
| `pia-proxy scale <N>` | **Mở rộng / thu gọn quy mô động** (ví dụ: `scale 20`, `scale 32`) Zero-Downtime |
|
|
31
|
+
| `pia-proxy rotate [ID] [--all]` | Kích hoạt xoay IP ngay lập tức cho 1 worker hoặc toàn bộ pool |
|
|
32
|
+
| `pia-proxy test [-n 5]` | Kiểm tra kết nối SOCKS5 gateway, đo độ trễ, đếm IP và xác nhận Zero-Leak |
|
|
33
|
+
| `pia-proxy export [-f format]` | **Xuất proxy định dạng sẵn** cho tool nuôi nick, Gologin, AdsPower, bot |
|
|
34
|
+
| `pia-proxy logs [-f]` | Theo dõi log real-time của worker hoặc gateway |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🚀 Đặc Điểm Nổi Bật
|
|
39
|
+
|
|
40
|
+
- **Cổng SOCKS5 Duy Nhất Cho Mọi Ứng Dụng:**
|
|
41
|
+
`socks5h://proxy_user:proxy_password_secure123@<IP_SERVER>:1087`
|
|
42
|
+
- **Cân Bằng Tải Thông Minh (Round-Robin):**
|
|
43
|
+
Mỗi kết nối / request mới tự động chuyển hướng qua một Worker VPN khác nhau, mang lại dải IP đa dạng và phân bổ đều.
|
|
44
|
+
- **Quy Tắc Xoay Vòng IP (Strict Rotation Rules):**
|
|
45
|
+
- Chu kỳ tự động xoay: **36s – 69s**.
|
|
46
|
+
- Giới hạn cứng Uptime: **70s** (Bất kỳ IP nào đạt 70s sẽ bị ép rotate ngay lập tức).
|
|
47
|
+
- Khống chế dung lượng: Không bao giờ để số worker khả dụng tụt dưới **20 IP**.
|
|
48
|
+
- **Tối Ưu Phần Cứng Tuyệt Đối (Zero CPU Overhead):**
|
|
49
|
+
- Chạy mượt mà ngay cả trên VPS 1 vCPU / 1GB RAM.
|
|
50
|
+
- Sử dụng lightweight TCP healthchecks trên cổng 1080, loại bỏ hoàn toàn hiện tượng 503 flapping và CPU spikes.
|
|
51
|
+
- **Bảo Mật Zero-Leak:**
|
|
52
|
+
- Tường lửa iptables killswitch trong từng worker chặn 100% rò rỉ IP gốc của VPS.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 📖 Ví Dụ Sử Dụng Nhanh
|
|
57
|
+
|
|
58
|
+
### 1. Xuất chuỗi proxy dùng cho phần mềm:
|
|
59
|
+
```bash
|
|
60
|
+
# Định dạng chuẩn IP:PORT:USER:PASS (dùng cho antidetect browser, bot, tool)
|
|
61
|
+
pia-proxy export --format standard
|
|
62
|
+
# Kết quả: 14.169.55.237:1087:proxy_user:proxy_password_secure123
|
|
63
|
+
|
|
64
|
+
# Định dạng URL:
|
|
65
|
+
pia-proxy export --format url
|
|
66
|
+
# Kết quả: socks5h://proxy_user:proxy_password_secure123@14.169.55.237:1087
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. Xem bảng trạng thái trực quan:
|
|
70
|
+
```bash
|
|
71
|
+
pia-proxy status
|
|
72
|
+
# Giám sát trực tiếp liên tục mỗi 2 giây:
|
|
73
|
+
pia-proxy status --watch -i 2
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3. Mở rộng số lượng worker khi cần:
|
|
77
|
+
```bash
|
|
78
|
+
# Tăng lên 32 worker:
|
|
79
|
+
pia-proxy scale 32
|
|
80
|
+
|
|
81
|
+
# Giảm xuống 20 worker:
|
|
82
|
+
pia-proxy scale 20
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 4. Kiểm thử chất lượng proxy & Zero-leak:
|
|
86
|
+
```bash
|
|
87
|
+
pia-proxy test --count 5
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🛠️ Triển Khai Thủ Công & Link Trực Tiếp
|
|
93
|
+
|
|
94
|
+
- **Web Dashboard:** `http://<IP_SERVER>:8007/`
|
|
95
|
+
- **API Status:** `http://<IP_SERVER>:8007/api/status`
|
|
96
|
+
- **HAProxy Live Stats:** `http://<IP_SERVER>:8411/`
|
|
97
|
+
- **Hướng dẫn cấu hình nâng cao:** [prod/README.md](prod/README.md)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import urllib.error
|
|
5
|
+
import urllib.parse
|
|
6
|
+
import urllib.request
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class APIClientError(Exception):
|
|
11
|
+
def __init__(self, message: str, status_code: int | None = None, response_body: Any = None):
|
|
12
|
+
super().__init__(message)
|
|
13
|
+
self.status_code = status_code
|
|
14
|
+
self.response_body = response_body
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class APIClient:
|
|
18
|
+
def __init__(self, base_url: str, token: str = "", timeout: float = 10.0):
|
|
19
|
+
self.base_url = base_url.rstrip("/")
|
|
20
|
+
self.token = token.strip()
|
|
21
|
+
self.timeout = timeout
|
|
22
|
+
|
|
23
|
+
def _request(
|
|
24
|
+
self,
|
|
25
|
+
method: str,
|
|
26
|
+
path: str,
|
|
27
|
+
params: dict[str, Any] | None = None,
|
|
28
|
+
json_data: dict[str, Any] | None = None,
|
|
29
|
+
timeout: float | None = None,
|
|
30
|
+
) -> dict[str, Any]:
|
|
31
|
+
url = f"{self.base_url}{path}"
|
|
32
|
+
if params:
|
|
33
|
+
query = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
|
|
34
|
+
if query:
|
|
35
|
+
url = f"{url}?{query}"
|
|
36
|
+
|
|
37
|
+
headers = {
|
|
38
|
+
"User-Agent": "pia-proxy-cli/1.0",
|
|
39
|
+
"Accept": "application/json",
|
|
40
|
+
}
|
|
41
|
+
if self.token:
|
|
42
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
43
|
+
|
|
44
|
+
data_bytes = None
|
|
45
|
+
if json_data is not None:
|
|
46
|
+
data_bytes = json.dumps(json_data).encode("utf-8")
|
|
47
|
+
headers["Content-Type"] = "application/json"
|
|
48
|
+
|
|
49
|
+
req = urllib.request.Request(url, data=data_bytes, headers=headers, method=method.upper())
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
with urllib.request.urlopen(req, timeout=timeout or self.timeout) as resp:
|
|
53
|
+
raw = resp.read().decode("utf-8")
|
|
54
|
+
if not raw.strip():
|
|
55
|
+
return {}
|
|
56
|
+
return json.loads(raw)
|
|
57
|
+
except urllib.error.HTTPError as e:
|
|
58
|
+
raw_err = e.read().decode("utf-8", errors="ignore")
|
|
59
|
+
try:
|
|
60
|
+
body = json.loads(raw_err)
|
|
61
|
+
except Exception:
|
|
62
|
+
body = raw_err
|
|
63
|
+
raise APIClientError(
|
|
64
|
+
message=f"HTTP {e.code}: {e.reason}",
|
|
65
|
+
status_code=e.code,
|
|
66
|
+
response_body=body,
|
|
67
|
+
) from e
|
|
68
|
+
except urllib.error.URLError as e:
|
|
69
|
+
raise APIClientError(message=f"Connection failed: {e.reason}") from e
|
|
70
|
+
except Exception as e:
|
|
71
|
+
raise APIClientError(message=f"Request error: {str(e)}") from e
|
|
72
|
+
|
|
73
|
+
def is_alive(self) -> bool:
|
|
74
|
+
try:
|
|
75
|
+
res = self._request("GET", "/healthz", timeout=3.0)
|
|
76
|
+
return bool(res.get("ok"))
|
|
77
|
+
except Exception:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
def get_status(self, refresh: bool = False) -> dict[str, Any]:
|
|
81
|
+
return self._request("GET", "/api/status", params={"refresh": refresh}, timeout=12.0)
|
|
82
|
+
|
|
83
|
+
def get_nodes(self, refresh: bool = False) -> list[dict[str, Any]]:
|
|
84
|
+
res = self._request("GET", "/api/nodes", params={"refresh": refresh}, timeout=10.0)
|
|
85
|
+
return res.get("nodes", [])
|
|
86
|
+
|
|
87
|
+
def get_healthy_nodes(self) -> dict[str, Any]:
|
|
88
|
+
return self._request("GET", "/api/nodes/healthy", timeout=6.0)
|
|
89
|
+
|
|
90
|
+
def get_node(self, node_id: str) -> dict[str, Any]:
|
|
91
|
+
return self._request("GET", f"/api/nodes/{node_id}", timeout=6.0)
|
|
92
|
+
|
|
93
|
+
def set_mode(self, worker_count: int) -> dict[str, Any]:
|
|
94
|
+
return self._request("POST", "/api/mode", json_data={"worker_count": worker_count}, timeout=30.0)
|
|
95
|
+
|
|
96
|
+
def get_mode(self) -> dict[str, Any]:
|
|
97
|
+
return self._request("GET", "/api/mode", timeout=5.0)
|
|
98
|
+
|
|
99
|
+
def rotate_any(self, country: str | None = None, server: str | None = None, wait_for_ready: bool = False) -> dict[str, Any]:
|
|
100
|
+
payload: dict[str, Any] = {"wait_for_ready": wait_for_ready}
|
|
101
|
+
if country:
|
|
102
|
+
payload["country"] = country
|
|
103
|
+
if server:
|
|
104
|
+
payload["server"] = server
|
|
105
|
+
return self._request("POST", "/api/rotate-any", json_data=payload, timeout=20.0)
|
|
106
|
+
|
|
107
|
+
def rotate_node(self, node_id: str, country: str | None = None, server: str | None = None, wait_for_ready: bool = False) -> dict[str, Any]:
|
|
108
|
+
payload: dict[str, Any] = {"wait_for_ready": wait_for_ready}
|
|
109
|
+
if country:
|
|
110
|
+
payload["country"] = country
|
|
111
|
+
if server:
|
|
112
|
+
payload["server"] = server
|
|
113
|
+
return self._request("POST", f"/api/nodes/{node_id}/rotate", json_data=payload, timeout=25.0)
|
|
114
|
+
|
|
115
|
+
def recover_all(self, force: bool = False) -> dict[str, Any]:
|
|
116
|
+
return self._request("POST", "/api/recover", json_data={"force": force}, timeout=15.0)
|
|
117
|
+
|
|
118
|
+
def recover_node(self, node_id: str, force: bool = False) -> dict[str, Any]:
|
|
119
|
+
return self._request("POST", f"/api/nodes/{node_id}/recover", json_data={"force": force}, timeout=15.0)
|
|
120
|
+
|
|
121
|
+
def test_proxy(self) -> dict[str, Any]:
|
|
122
|
+
return self._request("GET", "/api/test-proxy", timeout=15.0)
|
|
123
|
+
|
|
124
|
+
def get_countries(self, refresh: bool = False) -> dict[str, Any]:
|
|
125
|
+
return self._request("GET", "/api/countries", params={"refresh": refresh}, timeout=15.0)
|
|
126
|
+
|
|
127
|
+
def get_logs(self, tail: int = 100) -> dict[str, Any]:
|
|
128
|
+
return self._request("GET", "/api/logs", params={"tail": tail}, timeout=10.0)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from cli.config import CLIConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group("config", help="Manage proxy gateway credentials, environment settings, and ports.")
|
|
13
|
+
def config_group() -> None:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@config_group.command("show", help="Display all active configuration parameters.")
|
|
18
|
+
@click.pass_obj
|
|
19
|
+
def config_show(obj: dict[str, Any]) -> None:
|
|
20
|
+
config: CLIConfig = obj["config"]
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
table = Table(title="[bold cyan]PIA Proxy Configuration Summary[/bold cyan]", expand=True)
|
|
24
|
+
table.add_column("Parameter", style="bold white", width=24)
|
|
25
|
+
table.add_column("Value", style="yellow")
|
|
26
|
+
table.add_column("Location / Source", style="dim")
|
|
27
|
+
|
|
28
|
+
table.add_row("Project Root", str(config.project_root), "Detected")
|
|
29
|
+
table.add_row("Compose Dir", str(config.compose_dir), "Detected")
|
|
30
|
+
table.add_row(".env File", str(config.env_file), "Detected")
|
|
31
|
+
table.add_row("Credentials Dir", str(config.credentials_dir), "Detected")
|
|
32
|
+
table.add_row("SOCKS5 Proxy Port", str(config.proxy_port), ".env (PROXY_PORT)")
|
|
33
|
+
table.add_row("SOCKS5 Username", config.socks5_user, ".env (SOCKS5_USERNAME)")
|
|
34
|
+
table.add_row("SOCKS5 Password", "***" + config.socks5_pass[-4:] if len(config.socks5_pass) > 4 else "***", ".env (SOCKS5_PASSWORD)")
|
|
35
|
+
table.add_row("Backend API Port", str(config.api_port), ".env (API_PORT)")
|
|
36
|
+
table.add_row("Active Worker Count", str(config.worker_count), ".env (WORKER_COUNT)")
|
|
37
|
+
table.add_row("Max Worker Count", str(config.max_worker_count), ".env (MAX_WORKER_COUNT)")
|
|
38
|
+
|
|
39
|
+
u, p = config.get_pia_account()
|
|
40
|
+
if u and p:
|
|
41
|
+
table.add_row("PIA Account", f"{u} / {'*' * len(p)}", "credentials/pia_account_1")
|
|
42
|
+
else:
|
|
43
|
+
table.add_row("PIA Account", "[bold red]Not Configured![/bold red]", "credentials/pia_account_1")
|
|
44
|
+
|
|
45
|
+
session_file = config.credentials_dir / "pia_session_slot_1.json"
|
|
46
|
+
table.add_row("PIA Session Cache", "Present" if session_file.exists() else "Missing", "credentials/pia_session_slot_1.json")
|
|
47
|
+
|
|
48
|
+
console.print(table)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@config_group.command("set-credentials", help="Configure PIA account username and password.")
|
|
52
|
+
@click.option("--username", "-u", prompt=True, help="PIA Username (e.g. p1234567)")
|
|
53
|
+
@click.option("--password", "-p", prompt=True, hide_input=True, confirmation_prompt=True, help="PIA Password")
|
|
54
|
+
@click.pass_obj
|
|
55
|
+
def config_set_credentials(obj: dict[str, Any], username: str, password: str) -> None:
|
|
56
|
+
config: CLIConfig = obj["config"]
|
|
57
|
+
console = Console()
|
|
58
|
+
|
|
59
|
+
if not username.strip() or not password.strip():
|
|
60
|
+
console.print("[bold red]Username and password cannot be empty.[/bold red]")
|
|
61
|
+
raise SystemExit(1)
|
|
62
|
+
|
|
63
|
+
config.set_pia_account(username.strip(), password.strip())
|
|
64
|
+
console.print(f"[bold green]✔ Saved PIA account credentials to {config.credentials_dir / 'pia_account_1'}.[/bold green]")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@config_group.command("set-proxy-auth", help="Set username and password required for SOCKS5 proxy clients.")
|
|
68
|
+
@click.option("--username", "-u", prompt=True, help="Proxy username")
|
|
69
|
+
@click.option("--password", "-p", prompt=True, hide_input=True, confirmation_prompt=True, help="Proxy password")
|
|
70
|
+
@click.pass_obj
|
|
71
|
+
def config_set_proxy_auth(obj: dict[str, Any], username: str, password: str) -> None:
|
|
72
|
+
config: CLIConfig = obj["config"]
|
|
73
|
+
console = Console()
|
|
74
|
+
|
|
75
|
+
config.update_env_value("SOCKS5_USERNAME", username.strip())
|
|
76
|
+
config.update_env_value("SOCKS5_PASSWORD", password.strip())
|
|
77
|
+
console.print("[bold green]✔ Updated SOCKS5 credentials in .env.[/bold green]")
|
|
78
|
+
console.print("[dim]Note: Restart proxy pool with 'pia-proxy restart' to apply new proxy credentials.[/dim]")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@config_group.command("set-port", help="Set the external port for SOCKS5 gateway or backend API.")
|
|
82
|
+
@click.option("--proxy-port", type=int, default=None, help="New SOCKS5 gateway port (e.g. 1087, 1080)")
|
|
83
|
+
@click.option("--api-port", type=int, default=None, help="New Backend API port (e.g. 8007)")
|
|
84
|
+
@click.pass_obj
|
|
85
|
+
def config_set_port(obj: dict[str, Any], proxy_port: int | None, api_port: int | None) -> None:
|
|
86
|
+
config: CLIConfig = obj["config"]
|
|
87
|
+
console = Console()
|
|
88
|
+
|
|
89
|
+
if proxy_port:
|
|
90
|
+
config.update_env_value("PROXY_PORT", proxy_port)
|
|
91
|
+
console.print(f"[bold green]✔ Set PROXY_PORT={proxy_port} in .env.[/bold green]")
|
|
92
|
+
if api_port:
|
|
93
|
+
config.update_env_value("API_PORT", api_port)
|
|
94
|
+
console.print(f"[bold green]✔ Set API_PORT={api_port} in .env.[/bold green]")
|
|
95
|
+
|
|
96
|
+
console.print("[dim]Note: Restart proxy pool with 'pia-proxy restart' to apply port changes.[/dim]")
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import socket
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
from cli.config import CLIConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_port_open(port: int, host: str = "127.0.0.1") -> bool:
|
|
18
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
19
|
+
s.settimeout(0.5)
|
|
20
|
+
return s.connect_ex((host, port)) == 0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.command("doctor", help="Inspect system readiness, tun device, ports, swap, and Docker requirements.")
|
|
24
|
+
@click.pass_obj
|
|
25
|
+
def doctor_cmd(obj: dict[str, Any]) -> None:
|
|
26
|
+
config: CLIConfig = obj["config"]
|
|
27
|
+
console = Console()
|
|
28
|
+
|
|
29
|
+
table = Table(title="[bold cyan]System Environment & Health Diagnostics[/bold cyan]", expand=True)
|
|
30
|
+
table.add_column("Component", style="bold white", width=22)
|
|
31
|
+
table.add_column("Status", width=16)
|
|
32
|
+
table.add_column("Details", style="dim")
|
|
33
|
+
|
|
34
|
+
# 1. Docker Engine
|
|
35
|
+
docker_ok = False
|
|
36
|
+
try:
|
|
37
|
+
r = subprocess.run(["docker", "info"], capture_output=True, text=True, timeout=3)
|
|
38
|
+
docker_ok = r.returncode == 0
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
if docker_ok:
|
|
43
|
+
table.add_row("Docker Engine", Text("✔ OK", style="bold green"), "Daemon is active and reachable")
|
|
44
|
+
else:
|
|
45
|
+
table.add_row("Docker Engine", Text("✗ ERROR", style="bold red"), "Docker daemon not running or access denied")
|
|
46
|
+
|
|
47
|
+
# 2. Docker Compose
|
|
48
|
+
compose_ok = False
|
|
49
|
+
try:
|
|
50
|
+
r = subprocess.run(["docker", "compose", "version"], capture_output=True, text=True, timeout=3)
|
|
51
|
+
compose_ok = r.returncode == 0
|
|
52
|
+
compose_ver = r.stdout.strip()
|
|
53
|
+
except Exception:
|
|
54
|
+
compose_ver = "Not installed"
|
|
55
|
+
|
|
56
|
+
if compose_ok:
|
|
57
|
+
table.add_row("Docker Compose", Text("✔ OK", style="bold green"), compose_ver)
|
|
58
|
+
else:
|
|
59
|
+
table.add_row("Docker Compose", Text("✗ ERROR", style="bold red"), "docker compose command missing")
|
|
60
|
+
|
|
61
|
+
# 3. TUN device
|
|
62
|
+
tun_path = Path("/dev/net/tun")
|
|
63
|
+
if tun_path.exists():
|
|
64
|
+
table.add_row("TUN Device", Text("✔ OK", style="bold green"), "/dev/net/tun is present")
|
|
65
|
+
else:
|
|
66
|
+
table.add_row("TUN Device", Text("✗ MISSING", style="bold red"), "Run 'mkdir -p /dev/net && mknod /dev/net/tun c 10 200'")
|
|
67
|
+
|
|
68
|
+
# 4. RAM & Swap
|
|
69
|
+
mem_ok = False
|
|
70
|
+
mem_info = "Unable to read /proc/meminfo"
|
|
71
|
+
try:
|
|
72
|
+
with open("/proc/meminfo") as f:
|
|
73
|
+
lines = f.readlines()
|
|
74
|
+
total_ram = 0
|
|
75
|
+
total_swap = 0
|
|
76
|
+
for line in lines:
|
|
77
|
+
if line.startswith("MemTotal:"):
|
|
78
|
+
total_ram = int(line.split()[1]) // 1024
|
|
79
|
+
elif line.startswith("SwapTotal:"):
|
|
80
|
+
total_swap = int(line.split()[1]) // 1024
|
|
81
|
+
mem_info = f"RAM: {total_ram}MB | Swap: {total_swap}MB"
|
|
82
|
+
mem_ok = total_swap >= 1000 or total_ram >= 3000
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
if mem_ok:
|
|
87
|
+
table.add_row("Memory & Swap", Text("✔ OK", style="bold green"), mem_info)
|
|
88
|
+
else:
|
|
89
|
+
table.add_row("Memory & Swap", Text("⚠ WARN", style="bold yellow"), f"{mem_info} (Recommend adding >= 2GB swap)")
|
|
90
|
+
|
|
91
|
+
# 5. PIA Credentials
|
|
92
|
+
u, p = config.get_pia_account()
|
|
93
|
+
if u and p:
|
|
94
|
+
table.add_row("PIA Account", Text("✔ READY", style="bold green"), f"Username: {u}")
|
|
95
|
+
else:
|
|
96
|
+
table.add_row("PIA Account", Text("✗ NOT SET", style="bold red"), "Configure with 'pia-proxy config set-credentials'")
|
|
97
|
+
|
|
98
|
+
# 6. Gateway Port (1087)
|
|
99
|
+
gw_in_use = _is_port_open(config.proxy_port)
|
|
100
|
+
table.add_row(f"Proxy Port ({config.proxy_port})", Text("IN USE" if gw_in_use else "AVAILABLE", style="cyan" if gw_in_use else "green"), "SOCKS5 Proxy Gateway")
|
|
101
|
+
|
|
102
|
+
# 7. API Port (8007)
|
|
103
|
+
api_in_use = _is_port_open(config.api_port)
|
|
104
|
+
table.add_row(f"API Port ({config.api_port})", Text("IN USE" if api_in_use else "AVAILABLE", style="cyan" if api_in_use else "green"), "FastAPI Backend")
|
|
105
|
+
|
|
106
|
+
console.print(table)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from cli.config import CLIConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command("export", help="Export proxy connection strings formatted for bots, antidetect browsers, or crawlers.")
|
|
13
|
+
@click.option(
|
|
14
|
+
"--format",
|
|
15
|
+
"-f",
|
|
16
|
+
type=click.Choice(["url", "standard", "curl", "json", "env"]),
|
|
17
|
+
default="standard",
|
|
18
|
+
help="Output format (standard = IP:PORT:USER:PASS).",
|
|
19
|
+
show_default=True,
|
|
20
|
+
)
|
|
21
|
+
@click.option("--host", "-H", default=None, help="Custom server IP or hostname override.")
|
|
22
|
+
@click.pass_obj
|
|
23
|
+
def export_cmd(obj: dict[str, Any], format: str, host: str | None) -> None:
|
|
24
|
+
config: CLIConfig = obj["config"]
|
|
25
|
+
console = Console()
|
|
26
|
+
|
|
27
|
+
server_ip = host or config.get_public_ip()
|
|
28
|
+
port = config.proxy_port
|
|
29
|
+
user = config.socks5_user
|
|
30
|
+
pwd = config.socks5_pass
|
|
31
|
+
|
|
32
|
+
if format == "standard":
|
|
33
|
+
# IP:PORT:USER:PASS format commonly accepted by antidetect tools
|
|
34
|
+
click.echo(f"{server_ip}:{port}:{user}:{pwd}")
|
|
35
|
+
elif format == "url":
|
|
36
|
+
click.echo(f"socks5h://{user}:{pwd}@{server_ip}:{port}")
|
|
37
|
+
elif format == "curl":
|
|
38
|
+
click.echo(f"curl -x socks5h://{user}:{pwd}@{server_ip}:{port} https://api.ipify.org")
|
|
39
|
+
elif format == "json":
|
|
40
|
+
data = {
|
|
41
|
+
"type": "socks5",
|
|
42
|
+
"host": server_ip,
|
|
43
|
+
"port": port,
|
|
44
|
+
"username": user,
|
|
45
|
+
"password": pwd,
|
|
46
|
+
"url": f"socks5h://{user}:{pwd}@{server_ip}:{port}",
|
|
47
|
+
"standard": f"{server_ip}:{port}:{user}:{pwd}",
|
|
48
|
+
}
|
|
49
|
+
click.echo(json.dumps(data, indent=2))
|
|
50
|
+
elif format == "env":
|
|
51
|
+
click.echo(f'export ALL_PROXY="socks5h://{user}:{pwd}@{server_ip}:{port}"')
|
|
52
|
+
click.echo(f'export HTTP_PROXY="socks5h://{user}:{pwd}@{server_ip}:{port}"')
|
|
53
|
+
click.echo(f'export HTTPS_PROXY="socks5h://{user}:{pwd}@{server_ip}:{port}"')
|