sing-box-cli 0.0.14__tar.gz → 0.0.15__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.
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/CHANGELOG.md +23 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/PKG-INFO +1 -1
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/pyproject.toml +1 -1
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/__init__.py +1 -1
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/config.py +31 -18
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/main.py +44 -33
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/service.py +13 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/.github/dependabot.yml +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/.github/workflows/main.yml +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/.gitignore +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/.pre-commit-config.yaml +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/LICENSE +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/README.md +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/assets/image.png +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/client-android.json +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/client-linux.json +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/client-server.json +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/client-win.json +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/server.json +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/scripts/bump.sh +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/scripts/format.sh +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/scripts/lint.sh +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/scripts/pre-commit.sh +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/py.typed +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/tests/test_config.py +0 -0
- {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/uv.lock +0 -0
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.0.15 - 2025-03-10
|
|
6
|
+
|
|
7
|
+
### 🚀 Features
|
|
8
|
+
|
|
9
|
+
- Add version command
|
|
10
|
+
- Show config file path while invoking command
|
|
11
|
+
|
|
12
|
+
### 🐛 Bug Fixes
|
|
13
|
+
|
|
14
|
+
- Remove service file while disable service
|
|
15
|
+
- Potential crash while init directory
|
|
16
|
+
- Redundant logs
|
|
17
|
+
- Logs info
|
|
18
|
+
|
|
19
|
+
### 🚜 Refactor
|
|
20
|
+
|
|
21
|
+
- Remove update command
|
|
22
|
+
- Config info
|
|
23
|
+
|
|
24
|
+
### 📚 Documentation
|
|
25
|
+
|
|
26
|
+
- Fix function docstring
|
|
27
|
+
|
|
5
28
|
## 0.0.14 - 2025-03-10
|
|
6
29
|
|
|
7
30
|
### 🐛 Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sing-box-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.15
|
|
4
4
|
Summary: cross-platform sing-box-service manager.
|
|
5
5
|
Project-URL: Homepage, https://github.com/aittcuszz/sing-box-service
|
|
6
6
|
Project-URL: Issues, https://github.com/atticuszz/sing-box-service/issues
|
|
@@ -210,7 +210,7 @@ sort_commits = "oldest"
|
|
|
210
210
|
|
|
211
211
|
|
|
212
212
|
[tool.bumpversion]
|
|
213
|
-
current_version = "0.0.
|
|
213
|
+
current_version = "0.0.15"
|
|
214
214
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
215
215
|
serialize = ["{major}.{minor}.{patch}"]
|
|
216
216
|
search = "{current_version}"
|
|
@@ -25,7 +25,6 @@ class Config:
|
|
|
25
25
|
raise FileNotFoundError(f"❌ {bin_filename} not found in PATH")
|
|
26
26
|
|
|
27
27
|
self.bin_path = Path(bin_path)
|
|
28
|
-
print(f"🔧 Using binary: {self.bin_path}")
|
|
29
28
|
|
|
30
29
|
if self.is_windows:
|
|
31
30
|
self.install_dir = Path(os.environ["ProgramFiles"]) / "sing-box"
|
|
@@ -37,20 +36,27 @@ class Config:
|
|
|
37
36
|
self.subscription_file = self.install_dir / "subscription.txt"
|
|
38
37
|
self.cache_db = self.install_dir / "cache.db"
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
self.install_dir.mkdir(parents=True, exist_ok=True)
|
|
42
|
-
if not self.config_file.exists():
|
|
43
|
-
self.config_file.write_text("{}")
|
|
44
|
-
print(f"📁 Created empty config file: {self.config_file}")
|
|
39
|
+
print(self)
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
def init_directories(self) -> bool:
|
|
42
|
+
try:
|
|
43
|
+
self.install_dir.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
if not self.config_file.exists():
|
|
45
|
+
self.config_file.write_text("{}")
|
|
46
|
+
print(f"📁 Created empty config file: {self.config_file}")
|
|
47
|
+
|
|
48
|
+
if not self.subscription_file.exists():
|
|
49
|
+
self.subscription_file.touch()
|
|
50
|
+
print(f"📁 Created subscription file: {self.subscription_file}")
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
if not self.is_windows:
|
|
53
|
+
shutil.chown(self.install_dir, user=self.user, group=self.user)
|
|
54
|
+
shutil.chown(self.config_file, user=self.user, group=self.user)
|
|
55
|
+
shutil.chown(self.subscription_file, user=self.user, group=self.user)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"❌ Failed to initialize directories: {e}")
|
|
58
|
+
return False
|
|
59
|
+
return True
|
|
54
60
|
|
|
55
61
|
@property
|
|
56
62
|
def sub_url(self) -> str:
|
|
@@ -82,7 +88,6 @@ class Config:
|
|
|
82
88
|
print("📄 Configuration is up to date.")
|
|
83
89
|
else:
|
|
84
90
|
show_diff_config(current_config, new_config)
|
|
85
|
-
print("🔄 Configuration updated successfully")
|
|
86
91
|
|
|
87
92
|
return True
|
|
88
93
|
except Exception as e:
|
|
@@ -93,18 +98,16 @@ class Config:
|
|
|
93
98
|
if not url.startswith(("http://", "https://")):
|
|
94
99
|
print("❌ Invalid URL format.")
|
|
95
100
|
return False
|
|
96
|
-
self.subscription_file.write_text(url)
|
|
101
|
+
self.subscription_file.write_text(url.strip())
|
|
97
102
|
print("📁 Subscription added successfully.")
|
|
98
103
|
return True
|
|
99
104
|
|
|
100
105
|
def show_config(self) -> None:
|
|
101
|
-
print(f"📄 Configuration file: {self.config_file}")
|
|
102
106
|
print(self.config_file.read_text(encoding="utf-8"))
|
|
103
107
|
|
|
104
108
|
def show_subscription(self) -> None:
|
|
105
109
|
if self.sub_url:
|
|
106
|
-
print("🔗 Current subscription URL:")
|
|
107
|
-
print(self.sub_url)
|
|
110
|
+
print(f"🔗 Current subscription URL: {self.sub_url}")
|
|
108
111
|
else:
|
|
109
112
|
print("❌ No subscription URL found.")
|
|
110
113
|
|
|
@@ -115,6 +118,16 @@ class Config:
|
|
|
115
118
|
else:
|
|
116
119
|
print("❌ Cache database not found.")
|
|
117
120
|
|
|
121
|
+
def __str__(self) -> str:
|
|
122
|
+
info = (
|
|
123
|
+
f"🔧 Using binary: {self.bin_path}\n"
|
|
124
|
+
f"📄 Using configuration: {self.config_file}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if self.is_windows:
|
|
128
|
+
info += f"\n📁 Using installation directory: {self.install_dir}"
|
|
129
|
+
return info
|
|
130
|
+
|
|
118
131
|
|
|
119
132
|
def show_diff_config(current_config: str, new_config: str) -> None:
|
|
120
133
|
print("📄 Configuration differences:")
|
|
@@ -24,6 +24,8 @@ class SingBoxCLI:
|
|
|
24
24
|
if self.config.is_windows
|
|
25
25
|
else LinuxServiceManager(self.config)
|
|
26
26
|
)
|
|
27
|
+
if not self.config.init_directories():
|
|
28
|
+
typer.Exit(1)
|
|
27
29
|
|
|
28
30
|
def ensure_root(self) -> None:
|
|
29
31
|
"""https://gist.github.com/RDCH106/fdd419ef7dd803932b16056aab1d2300"""
|
|
@@ -39,42 +41,54 @@ class SingBoxCLI:
|
|
|
39
41
|
sys.exit(1)
|
|
40
42
|
|
|
41
43
|
|
|
42
|
-
@service.command("
|
|
43
|
-
def
|
|
44
|
-
"""
|
|
44
|
+
@service.command("enable")
|
|
45
|
+
def service_enable() -> None:
|
|
46
|
+
"""Create sing-box service, enable autostart and start service"""
|
|
45
47
|
cli = SingBoxCLI()
|
|
46
48
|
cli.ensure_root()
|
|
47
|
-
cli.config.init_directories()
|
|
48
49
|
cli.service.create_service()
|
|
49
50
|
cli.service.start()
|
|
50
|
-
print("
|
|
51
|
+
print("✔️ Service started.")
|
|
51
52
|
print("🔗 Dashboard URL: https://metacubexd.atticux.me/")
|
|
52
53
|
print("🔌 Default API: http://127.0.0.1:9090")
|
|
53
54
|
|
|
54
55
|
|
|
55
|
-
@service.command("
|
|
56
|
-
def
|
|
57
|
-
"""Stop sing-box service"""
|
|
56
|
+
@service.command("disable")
|
|
57
|
+
def service_disable() -> None:
|
|
58
|
+
"""Stop service, disable sing-box service autostart and remove service"""
|
|
58
59
|
cli = SingBoxCLI()
|
|
59
60
|
cli.ensure_root()
|
|
60
61
|
cli.service.stop()
|
|
61
|
-
|
|
62
|
+
cli.service.disable()
|
|
63
|
+
print("✋ Autostart disabled.")
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
@service.command("restart")
|
|
65
67
|
def service_restart() -> None:
|
|
66
|
-
"""Restart sing-box service"""
|
|
68
|
+
"""Restart sing-box service, update configuration if needed, create service if not exists"""
|
|
67
69
|
cli = SingBoxCLI()
|
|
68
70
|
cli.ensure_root()
|
|
69
71
|
if not cli.service.check_service():
|
|
70
72
|
cli.service.create_service()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
if cli.config.update_config():
|
|
74
|
+
cli.service.restart()
|
|
75
|
+
else:
|
|
76
|
+
print("❌ Failed to update configuration.")
|
|
77
|
+
typer.Exit(1)
|
|
78
|
+
print("✔️ Service restarted.")
|
|
74
79
|
print("🔗 Dashboard URL: https://metacubexd.atticux.me/")
|
|
75
80
|
print("🔌 Default API: http://127.0.0.1:9090")
|
|
76
81
|
|
|
77
82
|
|
|
83
|
+
@service.command("stop")
|
|
84
|
+
def service_stop() -> None:
|
|
85
|
+
"""Stop sing-box service"""
|
|
86
|
+
cli = SingBoxCLI()
|
|
87
|
+
cli.ensure_root()
|
|
88
|
+
cli.service.stop()
|
|
89
|
+
print("✋ Service stopped.")
|
|
90
|
+
|
|
91
|
+
|
|
78
92
|
@service.command("status")
|
|
79
93
|
def service_status() -> None:
|
|
80
94
|
"""Check service status"""
|
|
@@ -84,14 +98,11 @@ def service_status() -> None:
|
|
|
84
98
|
print(f"🏃 Service status: {status}")
|
|
85
99
|
|
|
86
100
|
|
|
87
|
-
@service.command("
|
|
88
|
-
def
|
|
89
|
-
"""
|
|
101
|
+
@service.command("logs")
|
|
102
|
+
def service_logs() -> None:
|
|
103
|
+
"""Show sing-box service logs"""
|
|
90
104
|
cli = SingBoxCLI()
|
|
91
|
-
cli.
|
|
92
|
-
cli.service.stop()
|
|
93
|
-
cli.service.disable()
|
|
94
|
-
print("✋ Autostart disabled.")
|
|
105
|
+
cli.service.logs()
|
|
95
106
|
|
|
96
107
|
|
|
97
108
|
@config.command("add-sub")
|
|
@@ -100,17 +111,14 @@ def config_add_sub(url: str) -> None:
|
|
|
100
111
|
cli = SingBoxCLI()
|
|
101
112
|
cli.ensure_root()
|
|
102
113
|
if cli.config.add_subscription(url):
|
|
114
|
+
# restart service if subscription is updated
|
|
115
|
+
if not cli.service.check_service():
|
|
116
|
+
cli.service.create_service()
|
|
117
|
+
print("⌛ Service created successfully.")
|
|
103
118
|
if cli.config.update_config():
|
|
104
119
|
cli.service.restart()
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@config.command("update")
|
|
108
|
-
def config_update() -> None:
|
|
109
|
-
"""Update configuration from subscription URL"""
|
|
110
|
-
cli = SingBoxCLI()
|
|
111
|
-
cli.ensure_root()
|
|
112
|
-
if cli.config.update_config():
|
|
113
|
-
cli.service.restart()
|
|
120
|
+
else:
|
|
121
|
+
typer.Exit(1)
|
|
114
122
|
|
|
115
123
|
|
|
116
124
|
@config.command("show-sub")
|
|
@@ -122,7 +130,7 @@ def config_show_sub() -> None:
|
|
|
122
130
|
|
|
123
131
|
@config.command("show")
|
|
124
132
|
def config_show() -> None:
|
|
125
|
-
"""Show configuration"""
|
|
133
|
+
"""Show configuration file"""
|
|
126
134
|
cli = SingBoxCLI()
|
|
127
135
|
cli.config.show_config()
|
|
128
136
|
|
|
@@ -135,10 +143,13 @@ def config_clean_cache() -> None:
|
|
|
135
143
|
|
|
136
144
|
|
|
137
145
|
@app.command()
|
|
138
|
-
def
|
|
139
|
-
"""Show
|
|
146
|
+
def version() -> None:
|
|
147
|
+
"""Show version"""
|
|
148
|
+
from . import __version__
|
|
149
|
+
|
|
140
150
|
cli = SingBoxCLI()
|
|
141
|
-
cli
|
|
151
|
+
print(f"🔖 sing-box-cli {__version__}")
|
|
152
|
+
print(f"📦 {cli.service.version()}")
|
|
142
153
|
|
|
143
154
|
|
|
144
155
|
def main() -> None:
|
|
@@ -32,6 +32,9 @@ class ServiceManager:
|
|
|
32
32
|
def disable(self) -> None:
|
|
33
33
|
raise NotImplementedError()
|
|
34
34
|
|
|
35
|
+
def version(self) -> str:
|
|
36
|
+
raise NotImplementedError()
|
|
37
|
+
|
|
35
38
|
|
|
36
39
|
class WindowsServiceManager(ServiceManager):
|
|
37
40
|
def __init__(self, config: Config):
|
|
@@ -150,6 +153,10 @@ if ($task) {{
|
|
|
150
153
|
]
|
|
151
154
|
)
|
|
152
155
|
|
|
156
|
+
def version(self) -> str:
|
|
157
|
+
result = subprocess.run([self.config.bin_path, "version"], capture_output=True)
|
|
158
|
+
return result.stdout.decode("utf-8").strip()
|
|
159
|
+
|
|
153
160
|
|
|
154
161
|
class LinuxServiceManager(ServiceManager):
|
|
155
162
|
def __init__(self, config: Config):
|
|
@@ -222,3 +229,9 @@ WantedBy=multi-user.target
|
|
|
222
229
|
def disable(self) -> None:
|
|
223
230
|
self.stop()
|
|
224
231
|
subprocess.run(["systemctl", "disable", self.service_name])
|
|
232
|
+
if self.service_file.exists():
|
|
233
|
+
self.service_file.unlink()
|
|
234
|
+
|
|
235
|
+
def version(self) -> str:
|
|
236
|
+
result = subprocess.run([self.config.bin_path, "version"], capture_output=True)
|
|
237
|
+
return result.stdout.decode("utf-8").strip()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|