proxctl 0.2.0__tar.gz → 0.2.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.
- {proxctl-0.2.0 → proxctl-0.2.2}/.github/workflows/ci.yml +15 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/.github/workflows/release.yml +80 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/CHANGELOG.md +15 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/Cargo.lock +1 -1
- {proxctl-0.2.0 → proxctl-0.2.2}/Cargo.toml +1 -1
- {proxctl-0.2.0 → proxctl-0.2.2}/Makefile +10 -1
- {proxctl-0.2.0 → proxctl-0.2.2}/PKG-INFO +65 -2
- {proxctl-0.2.0 → proxctl-0.2.2}/README.md +64 -1
- proxctl-0.2.2/prek.toml +36 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/container/config.rs +4 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/container/mod.rs +5 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/firewall.rs +34 -2
- {proxctl-0.2.0 → proxctl-0.2.2}/src/schema.rs +8 -0
- proxctl-0.2.0/.pre-commit-config.yaml +0 -31
- {proxctl-0.2.0 → proxctl-0.2.2}/.gitignore +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/LICENSE +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/proxctl_py/__init__.py +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/proxctl_py/__main__.py +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/pyproject.toml +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/api/client.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/api/error.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/api/mod.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/api/token.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/api/types.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/access.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/api.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/apply/container.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/apply/diff.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/apply/firewall.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/apply/manifest.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/apply/mod.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/apply/reconciler.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/apply/vm.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/backup.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/ceph.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/cluster.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/container/firewall.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/container/lifecycle.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/container/migrate.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/container/snapshot.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/export.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/mod.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/node.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/pool.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/storage.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/task.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/vm/agent.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/vm/cloudinit.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/vm/config.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/vm/firewall.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/vm/lifecycle.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/vm/migrate.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/vm/mod.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/commands/vm/snapshot.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/lib.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/main.rs +0 -0
- {proxctl-0.2.0 → proxctl-0.2.2}/src/output.rs +0 -0
|
@@ -35,6 +35,21 @@ jobs:
|
|
|
35
35
|
- name: Run tests
|
|
36
36
|
run: make test
|
|
37
37
|
|
|
38
|
+
coverage:
|
|
39
|
+
name: Coverage
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
44
|
+
- uses: taiki-e/install-action@cargo-tarpaulin
|
|
45
|
+
- name: Generate coverage
|
|
46
|
+
run: cargo tarpaulin --out xml
|
|
47
|
+
- name: Upload to Codecov
|
|
48
|
+
uses: codecov/codecov-action@v5
|
|
49
|
+
with:
|
|
50
|
+
files: cobertura.xml
|
|
51
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
52
|
+
|
|
38
53
|
all-checks-passed:
|
|
39
54
|
name: All checks passed
|
|
40
55
|
runs-on: ubuntu-latest
|
|
@@ -232,3 +232,83 @@ jobs:
|
|
|
232
232
|
echo "Dry run complete. Artifacts built but nothing published."
|
|
233
233
|
echo "Archives:"
|
|
234
234
|
find artifacts/release-* -type f | sort
|
|
235
|
+
|
|
236
|
+
update-homebrew:
|
|
237
|
+
needs: release
|
|
238
|
+
if: ${{ !inputs.dry_run }}
|
|
239
|
+
runs-on: ubuntu-latest
|
|
240
|
+
steps:
|
|
241
|
+
- uses: actions/download-artifact@v4
|
|
242
|
+
with:
|
|
243
|
+
path: /tmp/artifacts
|
|
244
|
+
|
|
245
|
+
- name: Compute SHA256 hashes
|
|
246
|
+
id: hashes
|
|
247
|
+
run: |
|
|
248
|
+
for target in x86_64-apple-darwin aarch64-apple-darwin x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do
|
|
249
|
+
sha=$(cat /tmp/artifacts/release-${target}/proxctl-${{ github.ref_name }}-${target}.tar.gz.sha256 | awk '{print $1}')
|
|
250
|
+
key=$(echo "${target}" | tr '-' '_')
|
|
251
|
+
echo "${key}=${sha}" >> "$GITHUB_OUTPUT"
|
|
252
|
+
done
|
|
253
|
+
|
|
254
|
+
- name: Update Homebrew formula
|
|
255
|
+
env:
|
|
256
|
+
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
|
257
|
+
run: |
|
|
258
|
+
VERSION="${{ github.ref_name }}"
|
|
259
|
+
VERSION_NUM="${VERSION#v}"
|
|
260
|
+
|
|
261
|
+
cat > /tmp/proxctl.rb << 'FORMULA'
|
|
262
|
+
class Proxctl < Formula
|
|
263
|
+
desc "CLI for Proxmox VE"
|
|
264
|
+
homepage "https://github.com/rvben/proxctl"
|
|
265
|
+
version "VERSION_NUM"
|
|
266
|
+
license "MIT"
|
|
267
|
+
|
|
268
|
+
on_macos do
|
|
269
|
+
if Hardware::CPU.arm?
|
|
270
|
+
url "https://github.com/rvben/proxctl/releases/download/VERSION/proxctl-VERSION-aarch64-apple-darwin.tar.gz"
|
|
271
|
+
sha256 "SHA_AARCH64_APPLE_DARWIN"
|
|
272
|
+
else
|
|
273
|
+
url "https://github.com/rvben/proxctl/releases/download/VERSION/proxctl-VERSION-x86_64-apple-darwin.tar.gz"
|
|
274
|
+
sha256 "SHA_X86_64_APPLE_DARWIN"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
on_linux do
|
|
279
|
+
if Hardware::CPU.arm?
|
|
280
|
+
url "https://github.com/rvben/proxctl/releases/download/VERSION/proxctl-VERSION-aarch64-unknown-linux-gnu.tar.gz"
|
|
281
|
+
sha256 "SHA_AARCH64_UNKNOWN_LINUX_GNU"
|
|
282
|
+
else
|
|
283
|
+
url "https://github.com/rvben/proxctl/releases/download/VERSION/proxctl-VERSION-x86_64-unknown-linux-gnu.tar.gz"
|
|
284
|
+
sha256 "SHA_X86_64_UNKNOWN_LINUX_GNU"
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def install
|
|
289
|
+
bin.install "proxctl"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
test do
|
|
293
|
+
system "#{bin}/proxctl", "--version"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
FORMULA
|
|
297
|
+
|
|
298
|
+
sed -i "s/VERSION_NUM/${VERSION_NUM}/g" /tmp/proxctl.rb
|
|
299
|
+
sed -i "s/VERSION/${VERSION}/g" /tmp/proxctl.rb
|
|
300
|
+
sed -i "s/SHA_AARCH64_APPLE_DARWIN/${{ steps.hashes.outputs.aarch64_apple_darwin }}/g" /tmp/proxctl.rb
|
|
301
|
+
sed -i "s/SHA_X86_64_APPLE_DARWIN/${{ steps.hashes.outputs.x86_64_apple_darwin }}/g" /tmp/proxctl.rb
|
|
302
|
+
sed -i "s/SHA_AARCH64_UNKNOWN_LINUX_GNU/${{ steps.hashes.outputs.aarch64_unknown_linux_gnu }}/g" /tmp/proxctl.rb
|
|
303
|
+
sed -i "s/SHA_X86_64_UNKNOWN_LINUX_GNU/${{ steps.hashes.outputs.x86_64_unknown_linux_gnu }}/g" /tmp/proxctl.rb
|
|
304
|
+
|
|
305
|
+
# Clone tap repo, update formula, push
|
|
306
|
+
git clone https://x-access-token:${GH_TOKEN}@github.com/rvben/homebrew-tap.git /tmp/tap
|
|
307
|
+
mkdir -p /tmp/tap/Formula
|
|
308
|
+
cp /tmp/proxctl.rb /tmp/tap/Formula/proxctl.rb
|
|
309
|
+
cd /tmp/tap
|
|
310
|
+
git config user.name "github-actions[bot]"
|
|
311
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
312
|
+
git add Formula/proxctl.rb
|
|
313
|
+
git diff --cached --quiet || git commit -m "Update proxctl to ${VERSION}"
|
|
314
|
+
git push
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## [0.2.2](https://github.com/rvben/proxctl/compare/v0.2.1...v0.2.2) - 2026-04-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **firewall**: add --iface and --macro flags to firewall add commands ([7533533](https://github.com/rvben/proxctl/commit/7533533da3bcc1e808cf3af5ffab6ca150d7a05b))
|
|
12
|
+
|
|
13
|
+
## [0.2.1](https://github.com/rvben/proxctl/compare/v0.2.0...v0.2.1) - 2026-03-28
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **container**: add --nameserver flag to container set command ([2cbdc9a](https://github.com/rvben/proxctl/commit/2cbdc9a421b3281731a48506cd3afb1f010752dd))
|
|
18
|
+
- **schema**: add metadata for apply and export commands ([b408baa](https://github.com/rvben/proxctl/commit/b408baa1c496af992bc4dd3d2da4611af5ce6b72))
|
|
19
|
+
|
|
5
20
|
## [0.2.0] - 2026-03-27
|
|
6
21
|
|
|
7
22
|
### Added
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: build release test lint fmt check clean install
|
|
1
|
+
.PHONY: build release test lint fmt check clean install release-patch release-minor release-major
|
|
2
2
|
|
|
3
3
|
build:
|
|
4
4
|
cargo build
|
|
@@ -23,3 +23,12 @@ clean:
|
|
|
23
23
|
|
|
24
24
|
install: release
|
|
25
25
|
cp target/release/proxctl ~/.local/bin/proxctl
|
|
26
|
+
|
|
27
|
+
release-patch:
|
|
28
|
+
vership bump patch
|
|
29
|
+
|
|
30
|
+
release-minor:
|
|
31
|
+
vership bump minor
|
|
32
|
+
|
|
33
|
+
release-major:
|
|
34
|
+
vership bump major
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxctl
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: System Administrators
|
|
@@ -24,8 +24,9 @@ Project-URL: Repository, https://github.com/rvben/proxctl.git
|
|
|
24
24
|
[](https://crates.io/crates/proxctl)
|
|
25
25
|
[](https://github.com/rvben/proxctl/actions/workflows/ci.yml)
|
|
26
26
|
[](LICENSE)
|
|
27
|
+
[](https://codecov.io/gh/rvben/proxctl)
|
|
27
28
|
|
|
28
|
-
A command-line interface for [Proxmox VE](https://www.proxmox.com/en/proxmox-virtual-environment/overview) -- manage VMs, containers, nodes, storage, and more from your terminal.
|
|
29
|
+
A command-line interface for [Proxmox VE](https://www.proxmox.com/en/proxmox-virtual-environment/overview) -- manage VMs, containers, nodes, storage, and more from your terminal. Includes declarative infrastructure management with `apply` and `export`.
|
|
29
30
|
|
|
30
31
|
## Install
|
|
31
32
|
|
|
@@ -65,9 +66,70 @@ proxctl vm snapshot list 100
|
|
|
65
66
|
proxctl api get /nodes
|
|
66
67
|
```
|
|
67
68
|
|
|
69
|
+
## Declarative Infrastructure (IaC)
|
|
70
|
+
|
|
71
|
+
Manage Proxmox resources declaratively with YAML manifests, similar to `kubectl apply`.
|
|
72
|
+
|
|
73
|
+
### Export existing resources
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Export a single VM
|
|
77
|
+
proxctl export vm 101 > haos.yaml
|
|
78
|
+
|
|
79
|
+
# Export all containers
|
|
80
|
+
proxctl export container --all > containers.yaml
|
|
81
|
+
|
|
82
|
+
# Export cluster firewall rules
|
|
83
|
+
proxctl export firewall cluster > firewall.yaml
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Apply desired state
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
# infra/web.yaml
|
|
90
|
+
kind: vm
|
|
91
|
+
name: web-01
|
|
92
|
+
vmid: 100
|
|
93
|
+
config:
|
|
94
|
+
memory: 4096
|
|
95
|
+
cores: 2
|
|
96
|
+
onboot: true
|
|
97
|
+
---
|
|
98
|
+
kind: firewall-rule
|
|
99
|
+
scope: cluster
|
|
100
|
+
config:
|
|
101
|
+
action: ACCEPT
|
|
102
|
+
type: in
|
|
103
|
+
proto: tcp
|
|
104
|
+
dport: "443"
|
|
105
|
+
comment: "Allow HTTPS"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Preview changes
|
|
110
|
+
proxctl apply -f infra/ --dry-run
|
|
111
|
+
|
|
112
|
+
# Apply changes
|
|
113
|
+
proxctl apply -f infra/
|
|
114
|
+
|
|
115
|
+
# Round-trip: export, then verify nothing drifted
|
|
116
|
+
proxctl export vm --all > current.yaml
|
|
117
|
+
proxctl apply -f current.yaml --dry-run # should show "noop" for all
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Key behaviors
|
|
121
|
+
|
|
122
|
+
- **Idempotent** -- running apply twice produces "up to date" on the second run
|
|
123
|
+
- **Patch semantics** -- only specified config keys are changed, others left untouched
|
|
124
|
+
- **Name or VMID** -- resources can be identified by name (auto-resolves VMID) or pinned by ID
|
|
125
|
+
- **Multi-document** -- multiple resources in one file with `---` separators, or a directory of files
|
|
126
|
+
- **Optional power state** -- add `state: running` or `state: stopped` to manage power, or omit to leave it alone
|
|
127
|
+
- **Safe** -- shows a diff before applying, destructive changes prompt for confirmation
|
|
128
|
+
|
|
68
129
|
## Features
|
|
69
130
|
|
|
70
131
|
- **145+ commands** covering VMs, containers, nodes, storage, backups, cluster, firewall, access control, pools, and Ceph
|
|
132
|
+
- **Declarative IaC** -- `apply` and `export` for infrastructure-as-code workflows
|
|
71
133
|
- **Auto-detection** -- resolves which node a VM lives on automatically
|
|
72
134
|
- **Agent-friendly** -- `--json` output, `schema` command for introspection, structured exit codes
|
|
73
135
|
- **Async task handling** -- waits for operations to complete with progress spinner
|
|
@@ -144,6 +206,7 @@ This enables AI agents and automation tools to discover available operations, re
|
|
|
144
206
|
|
|
145
207
|
| Feature | proxctl | pvesh (built-in) | proxmoxer (Python) |
|
|
146
208
|
|---|---|---|---|
|
|
209
|
+
| Declarative IaC (apply/export) | Yes | No | No |
|
|
147
210
|
| Typed CLI with completions | Yes | No | N/A |
|
|
148
211
|
| Cross-platform binaries | Yes | No (PVE only) | pip install |
|
|
149
212
|
| VMID auto-resolution | Yes | No | Manual |
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
[](https://crates.io/crates/proxctl)
|
|
4
4
|
[](https://github.com/rvben/proxctl/actions/workflows/ci.yml)
|
|
5
5
|
[](LICENSE)
|
|
6
|
+
[](https://codecov.io/gh/rvben/proxctl)
|
|
6
7
|
|
|
7
|
-
A command-line interface for [Proxmox VE](https://www.proxmox.com/en/proxmox-virtual-environment/overview) -- manage VMs, containers, nodes, storage, and more from your terminal.
|
|
8
|
+
A command-line interface for [Proxmox VE](https://www.proxmox.com/en/proxmox-virtual-environment/overview) -- manage VMs, containers, nodes, storage, and more from your terminal. Includes declarative infrastructure management with `apply` and `export`.
|
|
8
9
|
|
|
9
10
|
## Install
|
|
10
11
|
|
|
@@ -44,9 +45,70 @@ proxctl vm snapshot list 100
|
|
|
44
45
|
proxctl api get /nodes
|
|
45
46
|
```
|
|
46
47
|
|
|
48
|
+
## Declarative Infrastructure (IaC)
|
|
49
|
+
|
|
50
|
+
Manage Proxmox resources declaratively with YAML manifests, similar to `kubectl apply`.
|
|
51
|
+
|
|
52
|
+
### Export existing resources
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Export a single VM
|
|
56
|
+
proxctl export vm 101 > haos.yaml
|
|
57
|
+
|
|
58
|
+
# Export all containers
|
|
59
|
+
proxctl export container --all > containers.yaml
|
|
60
|
+
|
|
61
|
+
# Export cluster firewall rules
|
|
62
|
+
proxctl export firewall cluster > firewall.yaml
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Apply desired state
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
# infra/web.yaml
|
|
69
|
+
kind: vm
|
|
70
|
+
name: web-01
|
|
71
|
+
vmid: 100
|
|
72
|
+
config:
|
|
73
|
+
memory: 4096
|
|
74
|
+
cores: 2
|
|
75
|
+
onboot: true
|
|
76
|
+
---
|
|
77
|
+
kind: firewall-rule
|
|
78
|
+
scope: cluster
|
|
79
|
+
config:
|
|
80
|
+
action: ACCEPT
|
|
81
|
+
type: in
|
|
82
|
+
proto: tcp
|
|
83
|
+
dport: "443"
|
|
84
|
+
comment: "Allow HTTPS"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Preview changes
|
|
89
|
+
proxctl apply -f infra/ --dry-run
|
|
90
|
+
|
|
91
|
+
# Apply changes
|
|
92
|
+
proxctl apply -f infra/
|
|
93
|
+
|
|
94
|
+
# Round-trip: export, then verify nothing drifted
|
|
95
|
+
proxctl export vm --all > current.yaml
|
|
96
|
+
proxctl apply -f current.yaml --dry-run # should show "noop" for all
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Key behaviors
|
|
100
|
+
|
|
101
|
+
- **Idempotent** -- running apply twice produces "up to date" on the second run
|
|
102
|
+
- **Patch semantics** -- only specified config keys are changed, others left untouched
|
|
103
|
+
- **Name or VMID** -- resources can be identified by name (auto-resolves VMID) or pinned by ID
|
|
104
|
+
- **Multi-document** -- multiple resources in one file with `---` separators, or a directory of files
|
|
105
|
+
- **Optional power state** -- add `state: running` or `state: stopped` to manage power, or omit to leave it alone
|
|
106
|
+
- **Safe** -- shows a diff before applying, destructive changes prompt for confirmation
|
|
107
|
+
|
|
47
108
|
## Features
|
|
48
109
|
|
|
49
110
|
- **145+ commands** covering VMs, containers, nodes, storage, backups, cluster, firewall, access control, pools, and Ceph
|
|
111
|
+
- **Declarative IaC** -- `apply` and `export` for infrastructure-as-code workflows
|
|
50
112
|
- **Auto-detection** -- resolves which node a VM lives on automatically
|
|
51
113
|
- **Agent-friendly** -- `--json` output, `schema` command for introspection, structured exit codes
|
|
52
114
|
- **Async task handling** -- waits for operations to complete with progress spinner
|
|
@@ -123,6 +185,7 @@ This enables AI agents and automation tools to discover available operations, re
|
|
|
123
185
|
|
|
124
186
|
| Feature | proxctl | pvesh (built-in) | proxmoxer (Python) |
|
|
125
187
|
|---|---|---|---|
|
|
188
|
+
| Declarative IaC (apply/export) | Yes | No | No |
|
|
126
189
|
| Typed CLI with completions | Yes | No | N/A |
|
|
127
190
|
| Cross-platform binaries | Yes | No (PVE only) | pip install |
|
|
128
191
|
| VMID auto-resolution | Yes | No | Manual |
|
proxctl-0.2.2/prek.toml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#:schema: https://www.schemastore.org/prek.json
|
|
2
|
+
|
|
3
|
+
[[repos]]
|
|
4
|
+
repo = "builtin"
|
|
5
|
+
hooks = [
|
|
6
|
+
{ id = "trailing-whitespace" },
|
|
7
|
+
{ id = "end-of-file-fixer" },
|
|
8
|
+
{ id = "check-added-large-files" },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[[repos]]
|
|
12
|
+
repo = "local"
|
|
13
|
+
[[repos.hooks]]
|
|
14
|
+
id = "cargo-fmt"
|
|
15
|
+
name = "cargo fmt"
|
|
16
|
+
entry = "cargo fmt -- --check"
|
|
17
|
+
language = "system"
|
|
18
|
+
types = ["rust"]
|
|
19
|
+
pass_filenames = false
|
|
20
|
+
|
|
21
|
+
[[repos.hooks]]
|
|
22
|
+
id = "cargo-clippy"
|
|
23
|
+
name = "cargo clippy"
|
|
24
|
+
entry = "cargo clippy -- -D warnings"
|
|
25
|
+
language = "system"
|
|
26
|
+
types = ["rust"]
|
|
27
|
+
pass_filenames = false
|
|
28
|
+
|
|
29
|
+
[[repos.hooks]]
|
|
30
|
+
id = "cargo-test"
|
|
31
|
+
name = "cargo test"
|
|
32
|
+
entry = "cargo test"
|
|
33
|
+
language = "system"
|
|
34
|
+
types = ["rust"]
|
|
35
|
+
pass_filenames = false
|
|
36
|
+
stages = ["pre-push"]
|
|
@@ -66,6 +66,7 @@ pub async fn set(
|
|
|
66
66
|
hostname: Option<String>,
|
|
67
67
|
description: Option<String>,
|
|
68
68
|
onboot: Option<bool>,
|
|
69
|
+
nameserver: Option<String>,
|
|
69
70
|
) -> Result<(), Error> {
|
|
70
71
|
let node = client.resolve_node_for_vmid(vmid, node_override).await?;
|
|
71
72
|
let path = format!("/nodes/{node}/lxc/{vmid}/config");
|
|
@@ -86,6 +87,9 @@ pub async fn set(
|
|
|
86
87
|
if let Some(ob) = onboot {
|
|
87
88
|
params.push(("onboot".to_string(), if ob { "1" } else { "0" }.to_string()));
|
|
88
89
|
}
|
|
90
|
+
if let Some(ns) = nameserver {
|
|
91
|
+
params.push(("nameserver".to_string(), ns));
|
|
92
|
+
}
|
|
89
93
|
|
|
90
94
|
if params.is_empty() {
|
|
91
95
|
return Err(Error::Config(
|
|
@@ -192,6 +192,9 @@ pub enum ContainerCommand {
|
|
|
192
192
|
/// Start at boot
|
|
193
193
|
#[arg(long)]
|
|
194
194
|
onboot: Option<bool>,
|
|
195
|
+
/// DNS nameserver(s) (e.g. "10.10.30.225 1.1.1.1")
|
|
196
|
+
#[arg(long)]
|
|
197
|
+
nameserver: Option<String>,
|
|
195
198
|
},
|
|
196
199
|
/// Create a new container
|
|
197
200
|
Create {
|
|
@@ -362,6 +365,7 @@ pub async fn run(
|
|
|
362
365
|
hostname,
|
|
363
366
|
description,
|
|
364
367
|
onboot,
|
|
368
|
+
nameserver,
|
|
365
369
|
} => {
|
|
366
370
|
config::set(
|
|
367
371
|
client,
|
|
@@ -373,6 +377,7 @@ pub async fn run(
|
|
|
373
377
|
hostname,
|
|
374
378
|
description,
|
|
375
379
|
onboot,
|
|
380
|
+
nameserver,
|
|
376
381
|
)
|
|
377
382
|
.await
|
|
378
383
|
}
|
|
@@ -42,6 +42,9 @@ pub enum ClusterFirewallCommand {
|
|
|
42
42
|
/// Enable the rule
|
|
43
43
|
#[arg(long)]
|
|
44
44
|
enable: Option<bool>,
|
|
45
|
+
/// Network interface (e.g. vmbr0, vmbr0v30)
|
|
46
|
+
#[arg(long)]
|
|
47
|
+
iface: Option<String>,
|
|
45
48
|
/// Source address
|
|
46
49
|
#[arg(long)]
|
|
47
50
|
source: Option<String>,
|
|
@@ -54,6 +57,9 @@ pub enum ClusterFirewallCommand {
|
|
|
54
57
|
/// Protocol
|
|
55
58
|
#[arg(long)]
|
|
56
59
|
proto: Option<String>,
|
|
60
|
+
/// Macro (e.g. SSH, HTTP, HTTPS)
|
|
61
|
+
#[arg(long, rename_all = "kebab-case")]
|
|
62
|
+
r#macro: Option<String>,
|
|
57
63
|
/// Comment
|
|
58
64
|
#[arg(long)]
|
|
59
65
|
comment: Option<String>,
|
|
@@ -91,6 +97,9 @@ pub enum NodeFirewallCommand {
|
|
|
91
97
|
/// Enable the rule
|
|
92
98
|
#[arg(long)]
|
|
93
99
|
enable: Option<bool>,
|
|
100
|
+
/// Network interface (e.g. vmbr0, vmbr0v30)
|
|
101
|
+
#[arg(long)]
|
|
102
|
+
iface: Option<String>,
|
|
94
103
|
/// Source address
|
|
95
104
|
#[arg(long)]
|
|
96
105
|
source: Option<String>,
|
|
@@ -103,6 +112,9 @@ pub enum NodeFirewallCommand {
|
|
|
103
112
|
/// Protocol
|
|
104
113
|
#[arg(long)]
|
|
105
114
|
proto: Option<String>,
|
|
115
|
+
/// Macro (e.g. SSH, HTTP, HTTPS)
|
|
116
|
+
#[arg(long, rename_all = "kebab-case")]
|
|
117
|
+
r#macro: Option<String>,
|
|
106
118
|
/// Comment
|
|
107
119
|
#[arg(long)]
|
|
108
120
|
comment: Option<String>,
|
|
@@ -206,10 +218,12 @@ pub async fn run(
|
|
|
206
218
|
action,
|
|
207
219
|
r#type,
|
|
208
220
|
enable,
|
|
221
|
+
iface,
|
|
209
222
|
source,
|
|
210
223
|
dest,
|
|
211
224
|
dport,
|
|
212
225
|
proto,
|
|
226
|
+
r#macro,
|
|
213
227
|
comment,
|
|
214
228
|
} => {
|
|
215
229
|
cluster_add_rule(
|
|
@@ -218,10 +232,12 @@ pub async fn run(
|
|
|
218
232
|
&action,
|
|
219
233
|
&r#type,
|
|
220
234
|
enable,
|
|
235
|
+
iface.as_deref(),
|
|
221
236
|
source.as_deref(),
|
|
222
237
|
dest.as_deref(),
|
|
223
238
|
dport.as_deref(),
|
|
224
239
|
proto.as_deref(),
|
|
240
|
+
r#macro.as_deref(),
|
|
225
241
|
comment.as_deref(),
|
|
226
242
|
)
|
|
227
243
|
.await
|
|
@@ -240,10 +256,12 @@ pub async fn run(
|
|
|
240
256
|
action,
|
|
241
257
|
r#type,
|
|
242
258
|
enable,
|
|
259
|
+
iface,
|
|
243
260
|
source,
|
|
244
261
|
dest,
|
|
245
262
|
dport,
|
|
246
263
|
proto,
|
|
264
|
+
r#macro,
|
|
247
265
|
comment,
|
|
248
266
|
} => {
|
|
249
267
|
let n = require_node(node.as_deref(), global_node)?;
|
|
@@ -254,10 +272,12 @@ pub async fn run(
|
|
|
254
272
|
&action,
|
|
255
273
|
&r#type,
|
|
256
274
|
enable,
|
|
275
|
+
iface.as_deref(),
|
|
257
276
|
source.as_deref(),
|
|
258
277
|
dest.as_deref(),
|
|
259
278
|
dport.as_deref(),
|
|
260
279
|
proto.as_deref(),
|
|
280
|
+
r#macro.as_deref(),
|
|
261
281
|
comment.as_deref(),
|
|
262
282
|
)
|
|
263
283
|
.await
|
|
@@ -336,10 +356,12 @@ fn build_rule_params(
|
|
|
336
356
|
action: &str,
|
|
337
357
|
rule_type: &str,
|
|
338
358
|
enable: Option<bool>,
|
|
359
|
+
iface: Option<&str>,
|
|
339
360
|
source: Option<&str>,
|
|
340
361
|
dest: Option<&str>,
|
|
341
362
|
dport: Option<&str>,
|
|
342
363
|
proto: Option<&str>,
|
|
364
|
+
r#macro: Option<&str>,
|
|
343
365
|
comment: Option<&str>,
|
|
344
366
|
) -> Vec<(String, String)> {
|
|
345
367
|
let mut params: Vec<(String, String)> = vec![
|
|
@@ -349,6 +371,9 @@ fn build_rule_params(
|
|
|
349
371
|
if let Some(e) = enable {
|
|
350
372
|
params.push(("enable".to_string(), if e { "1" } else { "0" }.to_string()));
|
|
351
373
|
}
|
|
374
|
+
if let Some(i) = iface {
|
|
375
|
+
params.push(("iface".to_string(), i.to_string()));
|
|
376
|
+
}
|
|
352
377
|
if let Some(s) = source {
|
|
353
378
|
params.push(("source".to_string(), s.to_string()));
|
|
354
379
|
}
|
|
@@ -361,6 +386,9 @@ fn build_rule_params(
|
|
|
361
386
|
if let Some(p) = proto {
|
|
362
387
|
params.push(("proto".to_string(), p.to_string()));
|
|
363
388
|
}
|
|
389
|
+
if let Some(m) = r#macro {
|
|
390
|
+
params.push(("macro".to_string(), m.to_string()));
|
|
391
|
+
}
|
|
364
392
|
if let Some(c) = comment {
|
|
365
393
|
params.push(("comment".to_string(), c.to_string()));
|
|
366
394
|
}
|
|
@@ -391,14 +419,16 @@ async fn cluster_add_rule(
|
|
|
391
419
|
action: &str,
|
|
392
420
|
rule_type: &str,
|
|
393
421
|
enable: Option<bool>,
|
|
422
|
+
iface: Option<&str>,
|
|
394
423
|
source: Option<&str>,
|
|
395
424
|
dest: Option<&str>,
|
|
396
425
|
dport: Option<&str>,
|
|
397
426
|
proto: Option<&str>,
|
|
427
|
+
r#macro: Option<&str>,
|
|
398
428
|
comment: Option<&str>,
|
|
399
429
|
) -> Result<(), Error> {
|
|
400
430
|
let params = build_rule_params(
|
|
401
|
-
action, rule_type, enable, source, dest, dport, proto, comment,
|
|
431
|
+
action, rule_type, enable, iface, source, dest, dport, proto, r#macro, comment,
|
|
402
432
|
);
|
|
403
433
|
let param_refs: Vec<(&str, &str)> = params
|
|
404
434
|
.iter()
|
|
@@ -459,14 +489,16 @@ async fn node_add_rule(
|
|
|
459
489
|
action: &str,
|
|
460
490
|
rule_type: &str,
|
|
461
491
|
enable: Option<bool>,
|
|
492
|
+
iface: Option<&str>,
|
|
462
493
|
source: Option<&str>,
|
|
463
494
|
dest: Option<&str>,
|
|
464
495
|
dport: Option<&str>,
|
|
465
496
|
proto: Option<&str>,
|
|
497
|
+
r#macro: Option<&str>,
|
|
466
498
|
comment: Option<&str>,
|
|
467
499
|
) -> Result<(), Error> {
|
|
468
500
|
let params = build_rule_params(
|
|
469
|
-
action, rule_type, enable, source, dest, dport, proto, comment,
|
|
501
|
+
action, rule_type, enable, iface, source, dest, dport, proto, r#macro, comment,
|
|
470
502
|
);
|
|
471
503
|
let param_refs: Vec<(&str, &str)> = params
|
|
472
504
|
.iter()
|
|
@@ -381,6 +381,14 @@ fn build_metadata() -> HashMap<&'static str, CommandMeta> {
|
|
|
381
381
|
meta!("ceph pool create", mutating: true, idempotent: false);
|
|
382
382
|
meta!("ceph mon list", output_fields: &["name", "host", "addr"]);
|
|
383
383
|
|
|
384
|
+
// Apply
|
|
385
|
+
meta!("apply", mutating: true, idempotent: true, output_fields: &["kind", "name", "vmid", "action", "changes", "status", "error"]);
|
|
386
|
+
|
|
387
|
+
// Export
|
|
388
|
+
meta!("export vm", output_fields: &["kind", "name", "vmid", "node", "state", "config"]);
|
|
389
|
+
meta!("export container", output_fields: &["kind", "name", "vmid", "node", "state", "config"]);
|
|
390
|
+
meta!("export firewall", output_fields: &["kind", "scope", "target", "config"]);
|
|
391
|
+
|
|
384
392
|
// API passthrough
|
|
385
393
|
meta!("api get", output_fields: &[]);
|
|
386
394
|
meta!("api post", mutating: true, idempotent: false);
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
repos:
|
|
2
|
-
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
-
rev: v4.5.0
|
|
4
|
-
hooks:
|
|
5
|
-
- id: trailing-whitespace
|
|
6
|
-
- id: end-of-file-fixer
|
|
7
|
-
- id: check-added-large-files
|
|
8
|
-
|
|
9
|
-
- repo: local
|
|
10
|
-
hooks:
|
|
11
|
-
- id: cargo-fmt
|
|
12
|
-
name: cargo fmt
|
|
13
|
-
entry: cargo fmt -- --check
|
|
14
|
-
language: system
|
|
15
|
-
types: [rust]
|
|
16
|
-
pass_filenames: false
|
|
17
|
-
|
|
18
|
-
- id: cargo-clippy
|
|
19
|
-
name: cargo clippy
|
|
20
|
-
entry: cargo clippy -- -D warnings
|
|
21
|
-
language: system
|
|
22
|
-
types: [rust]
|
|
23
|
-
pass_filenames: false
|
|
24
|
-
|
|
25
|
-
- id: cargo-test
|
|
26
|
-
name: cargo test
|
|
27
|
-
entry: cargo test
|
|
28
|
-
language: system
|
|
29
|
-
types: [rust]
|
|
30
|
-
pass_filenames: false
|
|
31
|
-
stages: [pre-push]
|
|
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
|