feat: persist config from uvx bootstrap
This commit is contained in:
@@ -12,11 +12,21 @@ uvx --from git+https://gitea.shujk.top/shujakuin/shusub2.git shusub2 \
|
||||
--api-url https://codex.server2.shujk.top/1232131231313123/api/tui/accounts
|
||||
```
|
||||
|
||||
Bootstrap a new machine from `uvx`: save the API URL, install `shusub2` as a
|
||||
user command, then run it later as `shusub2`:
|
||||
|
||||
```bash
|
||||
uvx --from git+https://gitea.shujk.top/shujakuin/shusub2.git shusub2 \
|
||||
--api-url https://codex.server2.shujk.top/1232131231313123/api/tui/accounts \
|
||||
--install
|
||||
shusub2
|
||||
```
|
||||
|
||||
Install as a user command:
|
||||
|
||||
```bash
|
||||
uv tool install git+https://gitea.shujk.top/shujakuin/shusub2.git
|
||||
shusub2 --api-url https://codex.server2.shujk.top/1232131231313123/api/tui/accounts
|
||||
shusub2 --api-url https://codex.server2.shujk.top/1232131231313123/api/tui/accounts --save-config
|
||||
```
|
||||
|
||||
Configure the default public API URL for `shusub2`:
|
||||
@@ -58,6 +68,8 @@ Configuration:
|
||||
|
||||
- `--api-url` / `SUB2API_QUOTA_TUI_API_URL`
|
||||
- `--status-url` / `SHUSUB2_STATUS_URL`
|
||||
- `--save-config`
|
||||
- `--install`
|
||||
- `--refresh-seconds` / `SUB2API_QUOTA_TUI_REFRESH_SECONDS`
|
||||
- `--timeout` / `SUB2API_QUOTA_TUI_TIMEOUT`
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "shusub2"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
description = "Terminal UI for Sub2API account quota and daily usage"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
+41
-1
@@ -8,6 +8,7 @@ import importlib.metadata
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
@@ -16,7 +17,7 @@ from typing import Any
|
||||
|
||||
|
||||
APP_NAME = "shusub2"
|
||||
FALLBACK_VERSION = "0.1.6"
|
||||
FALLBACK_VERSION = "0.1.7"
|
||||
DEFAULT_API_URL = "http://127.0.0.1:18318/api/tui/accounts"
|
||||
DEFAULT_CONFIG_FILE = "~/.config/shusub2/api-url"
|
||||
DEFAULT_STATUS_CONFIG_FILE = "~/.config/shusub2/status-url"
|
||||
@@ -28,6 +29,7 @@ MONITOR_OK_STATUSES = {"operational", "ok", "success"}
|
||||
MONITOR_FAILED_STATUSES = {"error", "failed", "failure"}
|
||||
MONITOR_STOPWORDS = {"response", "responses", "monitor"}
|
||||
INSTALL_COMMAND = "uv tool install --force git+https://gitea.shujk.top/shujakuin/shusub2.git"
|
||||
INSTALL_COMMAND_ARGS = ["uv", "tool", "install", "--force", "git+https://gitea.shujk.top/shujakuin/shusub2.git"]
|
||||
|
||||
|
||||
def env_int(name: str, default: int, *, minimum: int = 1) -> int:
|
||||
@@ -68,6 +70,36 @@ def default_status_url() -> str:
|
||||
)
|
||||
|
||||
|
||||
def config_file_path() -> Path:
|
||||
return Path(os.environ.get("SHUSUB2_API_URL_FILE", DEFAULT_CONFIG_FILE)).expanduser()
|
||||
|
||||
|
||||
def write_api_url_config(api_url: str) -> Path:
|
||||
value = str(api_url or "").strip()
|
||||
if not value:
|
||||
raise ValueError("api url is empty")
|
||||
path = config_file_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
path.parent.chmod(0o700)
|
||||
except OSError:
|
||||
pass
|
||||
path.write_text(value + "\n", encoding="utf-8")
|
||||
try:
|
||||
path.chmod(0o600)
|
||||
except OSError:
|
||||
pass
|
||||
return path
|
||||
|
||||
|
||||
def run_install_command() -> int:
|
||||
try:
|
||||
return subprocess.run(INSTALL_COMMAND_ARGS, check=False).returncode
|
||||
except FileNotFoundError:
|
||||
print("uv command not found; install uv first, then run:", INSTALL_COMMAND, file=sys.stderr)
|
||||
return 127
|
||||
|
||||
|
||||
def inferred_status_url(api_url: str) -> str:
|
||||
parsed = urllib.parse.urlparse(str(api_url or "").strip())
|
||||
if not parsed.scheme or not parsed.netloc:
|
||||
@@ -624,6 +656,8 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Sub2API quota and daily usage TUI")
|
||||
parser.add_argument("--api-url", default=default_api_url())
|
||||
parser.add_argument("--status-url", default=default_status_url(), help="optional sub2api-status /api/status URL for channel monitor health")
|
||||
parser.add_argument("--save-config", action="store_true", help="persist --api-url to ~/.config/shusub2/api-url before running")
|
||||
parser.add_argument("--install", action="store_true", help="persist --api-url, install shusub2 as a uv tool, then exit")
|
||||
parser.add_argument("--version-check-url", default=os.environ.get("SHUSUB2_VERSION_CHECK_URL", DEFAULT_VERSION_CHECK_URL))
|
||||
parser.add_argument(
|
||||
"--version-check-timeout",
|
||||
@@ -645,6 +679,12 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
status_url = str(args.status_url or "").strip() or inferred_status_url(args.api_url)
|
||||
if args.save_config or args.install:
|
||||
config_path = write_api_url_config(args.api_url)
|
||||
print(f"saved api url to {config_path}")
|
||||
if args.install:
|
||||
print("installing shusub2 with uv tool...")
|
||||
return run_install_command()
|
||||
version_message = check_version_update(
|
||||
args.version_check_url,
|
||||
max(1, args.version_check_timeout),
|
||||
|
||||
+23
-4
@@ -2,9 +2,11 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
from pathlib import Path
|
||||
import importlib.util
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
@@ -98,10 +100,27 @@ class Sub2APIQuotaTUITests(unittest.TestCase):
|
||||
def test_version_update_message_only_for_newer_versions(self) -> None:
|
||||
mod = load_module()
|
||||
|
||||
self.assertEqual(mod.latest_version_from_text('name = "shusub2"\nversion = "0.1.7"\n'), "0.1.7")
|
||||
self.assertIn("0.1.6 -> 0.1.7", mod.version_update_message("0.1.7", "0.1.6"))
|
||||
self.assertEqual(mod.version_update_message("0.1.6", "0.1.6"), "")
|
||||
self.assertEqual(mod.version_update_message("0.1.5", "0.1.6"), "")
|
||||
self.assertEqual(mod.latest_version_from_text('name = "shusub2"\nversion = "0.1.8"\n'), "0.1.8")
|
||||
self.assertIn("0.1.7 -> 0.1.8", mod.version_update_message("0.1.8", "0.1.7"))
|
||||
self.assertEqual(mod.version_update_message("0.1.7", "0.1.7"), "")
|
||||
self.assertEqual(mod.version_update_message("0.1.6", "0.1.7"), "")
|
||||
|
||||
def test_write_api_url_config_uses_config_file_env(self) -> None:
|
||||
mod = load_module()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
config_file = Path(tmp) / "shusub2" / "api-url"
|
||||
old_value = os.environ.get("SHUSUB2_API_URL_FILE")
|
||||
os.environ["SHUSUB2_API_URL_FILE"] = str(config_file)
|
||||
try:
|
||||
written = mod.write_api_url_config("https://example.com/api/tui/accounts")
|
||||
self.assertEqual(written, config_file)
|
||||
self.assertEqual(config_file.read_text(encoding="utf-8"), "https://example.com/api/tui/accounts\n")
|
||||
finally:
|
||||
if old_value is None:
|
||||
os.environ.pop("SHUSUB2_API_URL_FILE", None)
|
||||
else:
|
||||
os.environ["SHUSUB2_API_URL_FILE"] = old_value
|
||||
|
||||
def test_once_output_is_name_first_and_includes_daily_quota(self) -> None:
|
||||
mod = load_module()
|
||||
|
||||
Reference in New Issue
Block a user