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.
Files changed (26) hide show
  1. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/CHANGELOG.md +23 -0
  2. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/PKG-INFO +1 -1
  3. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/pyproject.toml +1 -1
  4. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/__init__.py +1 -1
  5. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/config.py +31 -18
  6. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/main.py +44 -33
  7. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/service.py +13 -0
  8. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/.github/dependabot.yml +0 -0
  9. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/.github/workflows/main.yml +0 -0
  10. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/.gitignore +0 -0
  11. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/.pre-commit-config.yaml +0 -0
  12. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/LICENSE +0 -0
  13. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/README.md +0 -0
  14. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/assets/image.png +0 -0
  15. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/client-android.json +0 -0
  16. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/client-linux.json +0 -0
  17. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/client-server.json +0 -0
  18. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/client-win.json +0 -0
  19. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/config/server.json +0 -0
  20. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/scripts/bump.sh +0 -0
  21. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/scripts/format.sh +0 -0
  22. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/scripts/lint.sh +0 -0
  23. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/scripts/pre-commit.sh +0 -0
  24. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/src/sing_box_service/py.typed +0 -0
  25. {sing_box_cli-0.0.14 → sing_box_cli-0.0.15}/tests/test_config.py +0 -0
  26. {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.14
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.14"
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}"
@@ -2,4 +2,4 @@ from .main import main
2
2
 
3
3
  __all__ = ["main"]
4
4
 
5
- __version__ = "0.0.14"
5
+ __version__ = "0.0.15"
@@ -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
- def init_directories(self) -> None:
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
- if not self.subscription_file.exists():
47
- self.subscription_file.touch()
48
- print(f"📁 Created subscription file: {self.subscription_file}")
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
- if not self.is_windows:
51
- shutil.chown(self.install_dir, user=self.user, group=self.user)
52
- shutil.chown(self.config_file, user=self.user, group=self.user)
53
- shutil.chown(self.subscription_file, user=self.user, group=self.user)
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("start")
43
- def service_start() -> None:
44
- """Start sing-box service"""
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("✅ Service started.")
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("stop")
56
- def service_stop() -> None:
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
- print("✋ Service stopped.")
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
- print("⌛ Service created successfully.")
72
- cli.service.restart()
73
- print("✅ Service restarted.")
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("disable")
88
- def service_disable() -> None:
89
- """Disable sing-box service autostart"""
101
+ @service.command("logs")
102
+ def service_logs() -> None:
103
+ """Show sing-box service logs"""
90
104
  cli = SingBoxCLI()
91
- cli.ensure_root()
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 logs() -> None:
139
- """Show sing-box service logs"""
146
+ def version() -> None:
147
+ """Show version"""
148
+ from . import __version__
149
+
140
150
  cli = SingBoxCLI()
141
- cli.service.logs()
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