proxcli 0.16.1__tar.gz → 0.16.2__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.
- {proxcli-0.16.1 → proxcli-0.16.2}/CHANGELOG.md +9 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/PKG-INFO +9 -1
- {proxcli-0.16.1 → proxcli-0.16.2}/README.md +8 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/TODO.md +1 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/main.py +4 -2
- proxcli-0.16.2/proxmox/cli/update.py +154 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/pyproject.toml +1 -1
- {proxcli-0.16.1 → proxcli-0.16.2}/uv.lock +1 -1
- {proxcli-0.16.1 → proxcli-0.16.2}/.env.example +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/.github/workflows/ci.yml +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/.gitignore +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/.python-version +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/AGENTS.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/PLAN.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/PROJECT.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/PROMPT.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/DESIGN.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/api-coverage.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/api-permissions.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/assets/index-Bzx5-fXr.css +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/assets/index-CXpOjuxt.css +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/assets/index-DLDs0H6j.js +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/assets/index-DYQPMK8x.js +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/cloud-init.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/coding-agents.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/coverage.json +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/demos/cluster.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/demos/node-list.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/demos/vm-list.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/demos/vm-show.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/demos/yaml-spec.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/favicon.svg +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/icons.svg +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/index.html +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/production-automation.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/quickstart.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/.gitignore +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/README.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/build-all.sh +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/eslint.config.js +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/index.html +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/package-lock.json +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/package.json +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/public/coverage.json +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/public/demos/cluster.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/public/demos/node-list.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/public/demos/vm-list.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/public/demos/vm-show.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/public/demos/yaml-spec.gif +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/public/favicon.svg +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/public/icons.svg +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/.gitignore +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/.prettierrc +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/README.md +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/eslint.config.mjs +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/package-lock.json +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/package.json +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/public/cluster-real.txt +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/public/node-list-real.txt +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/public/vm-list-real.txt +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/public/vm-show-real.txt +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/remotion.config.ts +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/src/Composition.tsx +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/src/Root.tsx +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/src/index.css +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/src/index.ts +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/remotion-demos/tsconfig.json +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/src/App.jsx +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/src/components/CoverageGrid.jsx +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/src/components/SplitFlapAgent.jsx +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/src/index.css +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/src/main.jsx +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/src/pages/Docs.jsx +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/src/pages/Landing.jsx +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/docs/website/vite.config.js +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/acl.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/api.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/auth.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/backup.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/ceph.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/cluster.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/completion.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/container.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/firewall_helpers.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/network.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/node.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/pool.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/role.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/storage.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/tasks.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/user.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/vm.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/cli/vm_spec.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/client/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/client/auth.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/client/client.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/client/exceptions.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/config/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/config/config.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/config/models.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/output/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/output/formatter.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/output/json_fmt.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/output/log_fmt.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/output/table_fmt.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/output/yaml_fmt.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/utils/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/utils/helpers.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/proxmox/utils/logging.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/conftest.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_auth.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_api.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_backup.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_backup_restore.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_ceph.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_container_ip.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_main.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_network.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_node_system.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_role_acl.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_task_wait.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_user.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_vm_agent.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_vm_clone.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_vm_disk.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_vm_ip.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_vm_iso.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_vm_migrate.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_vm_set.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_cli/test_vm_template.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_client.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_config.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_integration/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_output/__init__.py +0 -0
- {proxcli-0.16.1 → proxcli-0.16.2}/tests/test_output/test_formatter.py +0 -0
|
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.16.2] - 2026-06-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **``proxmox update``**: self-upgrade proxcli from PyPI. Queries the PyPI JSON
|
|
12
|
+
API to check for new versions and uses ``uv tool install proxcli --reinstall``
|
|
13
|
+
to upgrade in-place. ``--check`` flag reports available updates without
|
|
14
|
+
installing. ``--pre`` includes pre-release versions.
|
|
15
|
+
|
|
8
16
|
## [0.16.1] - 2026-06-30
|
|
9
17
|
|
|
10
18
|
### Added
|
|
@@ -355,6 +363,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
355
363
|
- CSRF ticket auto-refresh on 401.
|
|
356
364
|
- AI-agent-friendly: default JSON output, strict exit codes, `--dry-run` mode.
|
|
357
365
|
|
|
366
|
+
[0.16.2]: https://github.com/xezpeleta/proxcli/releases/tag/v0.16.2
|
|
358
367
|
[0.16.1]: https://github.com/xezpeleta/proxcli/releases/tag/v0.16.1
|
|
359
368
|
[0.16.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.16.0
|
|
360
369
|
[0.15.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.15.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxcli
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.2
|
|
4
4
|
Summary: A CLI tool to interact with Proxmox VE nodes and clusters via the REST API
|
|
5
5
|
Author-email: Xabi Ezpeleta <xezpeleta@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -192,6 +192,14 @@ proxmox auth setup # Create recommended roles + ACLs (needs Administ
|
|
|
192
192
|
proxmox auth check # Live permission test table (39 checks)
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
### Self-update
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
proxmox update # Check for updates and install the latest version
|
|
199
|
+
proxmox update --check # Only check (no install)
|
|
200
|
+
proxmox update --pre # Include pre-release versions
|
|
201
|
+
```
|
|
202
|
+
|
|
195
203
|
### Completion
|
|
196
204
|
|
|
197
205
|
```bash
|
|
@@ -169,6 +169,14 @@ proxmox auth setup # Create recommended roles + ACLs (needs Administ
|
|
|
169
169
|
proxmox auth check # Live permission test table (39 checks)
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
+
### Self-update
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
proxmox update # Check for updates and install the latest version
|
|
176
|
+
proxmox update --check # Only check (no install)
|
|
177
|
+
proxmox update --pre # Include pre-release versions
|
|
178
|
+
```
|
|
179
|
+
|
|
172
180
|
### Completion
|
|
173
181
|
|
|
174
182
|
```bash
|
|
@@ -29,6 +29,7 @@ Completed items are marked with a check. Implementation notes are preserved for
|
|
|
29
29
|
- [x] **API coverage doc** — `docs/api-coverage.md` tracking all implemented and remaining endpoints.
|
|
30
30
|
- [x] **VM disk import from URL** — `vm disk import --url` downloads, uploads, and imports a disk image from a remote URL in one command. `--image` for local PVE node paths.
|
|
31
31
|
- [x] **Cloud-init auto serial console** — `vm create` with cloud-init flags now auto-adds `serial0=socket` and `vga=serial0`, required by Debian generic cloud images.
|
|
32
|
+
- [x] **Self-update** — `proxmox update` checks PyPI for newer versions and upgrades via `uv tool install --reinstall`. `--check` for dry-run, `--pre` for pre-releases.
|
|
32
33
|
|
|
33
34
|
## v1.1 — Polish & Usability
|
|
34
35
|
|
|
@@ -74,6 +74,7 @@ def build_root_parser() -> argparse.ArgumentParser:
|
|
|
74
74
|
from proxmox.cli.role import register_role_parser
|
|
75
75
|
from proxmox.cli.storage import register_storage_parser
|
|
76
76
|
from proxmox.cli.tasks import register_task_parser
|
|
77
|
+
from proxmox.cli.update import register_update_parser
|
|
77
78
|
from proxmox.cli.user import register_user_parser
|
|
78
79
|
from proxmox.cli.vm import register_vm_parser
|
|
79
80
|
|
|
@@ -93,6 +94,7 @@ def build_root_parser() -> argparse.ArgumentParser:
|
|
|
93
94
|
register_task_parser(subparsers)
|
|
94
95
|
register_role_parser(subparsers)
|
|
95
96
|
register_user_parser(subparsers)
|
|
97
|
+
register_update_parser(subparsers)
|
|
96
98
|
|
|
97
99
|
return parser
|
|
98
100
|
|
|
@@ -224,7 +226,7 @@ GLOBAL_FLAGS_WITH_VALUE = {"--url", "--username", "--password", "--api-token", "
|
|
|
224
226
|
def _hint_global_flags_order(argv: list[str]) -> None:
|
|
225
227
|
"""If user placed global flags after the resource, show a helpful hint."""
|
|
226
228
|
resource_pos = -1
|
|
227
|
-
resources = {"acl", "api", "auth", "backup", "ceph", "vm", "node", "pool", "container", "storage", "cluster", "completion", "task", "user", "role", "network"}
|
|
229
|
+
resources = {"acl", "api", "auth", "backup", "ceph", "vm", "node", "pool", "container", "storage", "cluster", "completion", "task", "user", "role", "network", "update"}
|
|
228
230
|
for i, arg in enumerate(argv):
|
|
229
231
|
if arg in resources:
|
|
230
232
|
resource_pos = i
|
|
@@ -297,7 +299,7 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
297
299
|
|
|
298
300
|
try:
|
|
299
301
|
# auth status (without --permissions) and completion don't need a client
|
|
300
|
-
if args.resource == "completion" or (
|
|
302
|
+
if args.resource == "completion" or args.resource == "update" or (
|
|
301
303
|
args.resource == "auth"
|
|
302
304
|
and args.action == "status"
|
|
303
305
|
and not getattr(args, "permissions", False)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""proxmox update — self-upgrade proxcli from PyPI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import urllib.request
|
|
10
|
+
from importlib.metadata import version
|
|
11
|
+
|
|
12
|
+
from proxmox.utils.logging import log_error
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register_update_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
16
|
+
update_parser = subparsers.add_parser("update", help="Check for and install proxcli updates")
|
|
17
|
+
update_parser.add_argument(
|
|
18
|
+
"--check",
|
|
19
|
+
action="store_true",
|
|
20
|
+
help="Only check if an update is available (do not install)",
|
|
21
|
+
)
|
|
22
|
+
update_parser.add_argument(
|
|
23
|
+
"--pre",
|
|
24
|
+
action="store_true",
|
|
25
|
+
help="Include pre-release versions when checking",
|
|
26
|
+
)
|
|
27
|
+
update_parser.set_defaults(func=_update, output_format="table")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_latest_version(include_pre: bool = False) -> str:
|
|
31
|
+
"""Fetch the latest version tag from PyPI."""
|
|
32
|
+
url = "https://pypi.org/pypi/proxcli/json"
|
|
33
|
+
try:
|
|
34
|
+
with urllib.request.urlopen(url, timeout=10) as resp:
|
|
35
|
+
data = json.loads(resp.read().decode())
|
|
36
|
+
except Exception as exc:
|
|
37
|
+
raise RuntimeError(f"Failed to query PyPI: {exc}") from exc
|
|
38
|
+
|
|
39
|
+
versions = list(data.get("releases", {}).keys())
|
|
40
|
+
|
|
41
|
+
if not versions:
|
|
42
|
+
raise RuntimeError("No releases found on PyPI")
|
|
43
|
+
|
|
44
|
+
if not include_pre:
|
|
45
|
+
versions = [v for v in versions if _is_stable(v)]
|
|
46
|
+
if not versions:
|
|
47
|
+
raise RuntimeError("No stable releases found on PyPI")
|
|
48
|
+
|
|
49
|
+
return max(versions, key=_version_key)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _is_stable(ver: str) -> bool:
|
|
53
|
+
"""Return True if version looks like a stable release (no dev/pre markers)."""
|
|
54
|
+
# Strip epoch if present
|
|
55
|
+
v = ver
|
|
56
|
+
# Simple check: no a/b/rc/dev/post markers
|
|
57
|
+
for marker in ("a", "b", "rc", "dev", "post"):
|
|
58
|
+
if marker in v:
|
|
59
|
+
return False
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _version_key(ver: str) -> tuple:
|
|
64
|
+
"""Parse a version string into a comparable tuple."""
|
|
65
|
+
parts = ver.split(".")
|
|
66
|
+
result = []
|
|
67
|
+
for p in parts:
|
|
68
|
+
# Try int first
|
|
69
|
+
try:
|
|
70
|
+
result.append(int(p))
|
|
71
|
+
except ValueError:
|
|
72
|
+
result.append(p)
|
|
73
|
+
return tuple(result)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _update(args: argparse.Namespace, client: object | None = None) -> dict | str:
|
|
77
|
+
"""Check for updates and optionally install them.
|
|
78
|
+
|
|
79
|
+
Returns a dict for the output formatter.
|
|
80
|
+
"""
|
|
81
|
+
current = version("proxcli")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
latest = _get_latest_version(include_pre=args.pre)
|
|
85
|
+
except RuntimeError as exc:
|
|
86
|
+
return {"error": str(exc)}
|
|
87
|
+
|
|
88
|
+
up_to_date = _version_key(current) >= _version_key(latest)
|
|
89
|
+
|
|
90
|
+
if args.check:
|
|
91
|
+
if up_to_date:
|
|
92
|
+
return {
|
|
93
|
+
"current": current,
|
|
94
|
+
"latest": latest,
|
|
95
|
+
"status": "up-to-date",
|
|
96
|
+
"message": f"proxcli {current} is the latest version.",
|
|
97
|
+
}
|
|
98
|
+
else:
|
|
99
|
+
return {
|
|
100
|
+
"current": current,
|
|
101
|
+
"latest": latest,
|
|
102
|
+
"status": "update-available",
|
|
103
|
+
"message": f"proxcli {latest} is available (you have {current}). Run 'proxmox update' to upgrade.",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if up_to_date:
|
|
107
|
+
return {
|
|
108
|
+
"current": current,
|
|
109
|
+
"latest": latest,
|
|
110
|
+
"status": "up-to-date",
|
|
111
|
+
"message": f"proxcli {current} is already the latest version.",
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Run uv tool install
|
|
115
|
+
log_error(f"Upgrading proxcli from {current} to {latest}...", file=sys.stderr)
|
|
116
|
+
try:
|
|
117
|
+
result = subprocess.run(
|
|
118
|
+
["uv", "tool", "install", "proxcli", "--reinstall"],
|
|
119
|
+
capture_output=True,
|
|
120
|
+
text=True,
|
|
121
|
+
timeout=120,
|
|
122
|
+
)
|
|
123
|
+
if result.returncode != 0:
|
|
124
|
+
error_msg = result.stderr.strip() or result.stdout.strip() or f"exit code {result.returncode}"
|
|
125
|
+
log_error(error_msg)
|
|
126
|
+
return {
|
|
127
|
+
"current": current,
|
|
128
|
+
"latest": latest,
|
|
129
|
+
"status": "error",
|
|
130
|
+
"message": f"Upgrade failed: {error_msg}",
|
|
131
|
+
}
|
|
132
|
+
except FileNotFoundError:
|
|
133
|
+
return {
|
|
134
|
+
"current": current,
|
|
135
|
+
"latest": latest,
|
|
136
|
+
"status": "error",
|
|
137
|
+
"message": "uv not found in PATH. Install uv (https://docs.astral.sh/uv/) or upgrade manually with 'uv tool install proxcli --reinstall'.",
|
|
138
|
+
}
|
|
139
|
+
except subprocess.TimeoutExpired:
|
|
140
|
+
return {
|
|
141
|
+
"current": current,
|
|
142
|
+
"latest": latest,
|
|
143
|
+
"status": "error",
|
|
144
|
+
"message": "Upgrade timed out. Try again or upgrade manually.",
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
new_version = version("proxcli")
|
|
148
|
+
return {
|
|
149
|
+
"current": new_version,
|
|
150
|
+
"previous": current,
|
|
151
|
+
"latest": latest,
|
|
152
|
+
"status": "upgraded",
|
|
153
|
+
"message": f"proxcli upgraded from {current} to {new_version}.",
|
|
154
|
+
}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|