proxcli 0.4.0__tar.gz → 0.6.0__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.4.0 → proxcli-0.6.0}/CHANGELOG.md +15 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/PKG-INFO +125 -1
- {proxcli-0.4.0 → proxcli-0.6.0}/README.md +124 -0
- proxcli-0.6.0/proxmox/cli/completion.py +214 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/main.py +12 -4
- proxcli-0.6.0/proxmox/cli/pool.py +74 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/pyproject.toml +1 -1
- {proxcli-0.4.0 → proxcli-0.6.0}/uv.lock +1 -1
- {proxcli-0.4.0 → proxcli-0.6.0}/.env.example +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/.github/workflows/ci.yml +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/.gitignore +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/.python-version +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/AGENTS.md +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/PLAN.md +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/PROJECT.md +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/PROMPT.md +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/TODO.md +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/auth.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/cluster.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/container.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/firewall_helpers.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/node.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/storage.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/tasks.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/cli/vm.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/client/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/client/auth.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/client/client.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/client/exceptions.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/config/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/config/config.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/config/models.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/output/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/output/formatter.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/output/json_fmt.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/output/table_fmt.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/output/yaml_fmt.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/utils/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/utils/helpers.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/proxmox/utils/logging.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/conftest.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/test_auth.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/test_cli/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/test_cli/test_main.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/test_client.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/test_config.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/test_integration/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/test_output/__init__.py +0 -0
- {proxcli-0.4.0 → proxcli-0.6.0}/tests/test_output/test_formatter.py +0 -0
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-06-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Shell completion support: `proxmox completion bash|zsh|fish`.
|
|
14
|
+
Generated scripts introspect the parser tree and stay in sync
|
|
15
|
+
with all registered resources and actions.
|
|
16
|
+
|
|
17
|
+
## [0.5.0] - 2026-06-20
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Pool management (`proxmox pool`): list, show, create, update, delete.
|
|
21
|
+
Wraps `/pools` endpoints.
|
|
22
|
+
|
|
10
23
|
## [0.4.0] - 2026-06-20
|
|
11
24
|
|
|
12
25
|
### Added
|
|
@@ -61,6 +74,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
61
74
|
- CSRF ticket auto-refresh on 401.
|
|
62
75
|
- AI-agent-friendly: default JSON output, strict exit codes, `--dry-run` mode.
|
|
63
76
|
|
|
77
|
+
[0.6.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.6.0
|
|
78
|
+
[0.5.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.5.0
|
|
64
79
|
[0.4.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.4.0
|
|
65
80
|
[0.3.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.3.0
|
|
66
81
|
[0.2.1]: https://github.com/xezpeleta/proxcli/releases/tag/v0.2.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxcli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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
|
|
@@ -51,6 +51,11 @@ proxmox auth login --url https://192.168.1.10:8006 --username root@pam --passwor
|
|
|
51
51
|
# Or with an API token
|
|
52
52
|
proxmox auth login --url https://192.168.1.10:8006 --username root@pam --api-token 'root@pam!my-token=deadbeef...'
|
|
53
53
|
|
|
54
|
+
# Enable shell completions
|
|
55
|
+
source <(proxmox completion bash) # bash
|
|
56
|
+
source <(proxmox completion zsh) # zsh
|
|
57
|
+
proxmox completion fish | source # fish (or save to ~/.config/fish/completions/proxmox.fish)
|
|
58
|
+
|
|
54
59
|
# Check auth status
|
|
55
60
|
proxmox auth status
|
|
56
61
|
|
|
@@ -129,6 +134,27 @@ proxmox auth status # Show current auth context
|
|
|
129
134
|
proxmox auth clear # Remove saved credentials
|
|
130
135
|
```
|
|
131
136
|
|
|
137
|
+
### Completion
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
proxmox completion bash # Emit bash completion script
|
|
141
|
+
proxmox completion zsh # Emit zsh completion script
|
|
142
|
+
proxmox completion fish # Emit fish completion script
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Add to your shell's rc file:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# bash (~/.bashrc)
|
|
149
|
+
source <(proxmox completion bash)
|
|
150
|
+
|
|
151
|
+
# zsh (~/.zshrc)
|
|
152
|
+
source <(proxmox completion zsh)
|
|
153
|
+
|
|
154
|
+
# fish (~/.config/fish/completions/proxmox.fish)
|
|
155
|
+
proxmox completion fish > ~/.config/fish/completions/proxmox.fish
|
|
156
|
+
```
|
|
157
|
+
|
|
132
158
|
### VM (QEMU)
|
|
133
159
|
|
|
134
160
|
```bash
|
|
@@ -141,6 +167,18 @@ proxmox vm reboot <vmid> [--node <node>]
|
|
|
141
167
|
proxmox vm suspend <vmid> [--node <node>]
|
|
142
168
|
proxmox vm resume <vmid> [--node <node>]
|
|
143
169
|
proxmox vm delete <vmid> [--node <node>] [--force] [--purge]
|
|
170
|
+
|
|
171
|
+
# VM firewall
|
|
172
|
+
proxmox vm firewall options <vmid> [--node <node>]
|
|
173
|
+
proxmox vm firewall enable <vmid> [--node <node>]
|
|
174
|
+
proxmox vm firewall disable <vmid> [--node <node>]
|
|
175
|
+
proxmox vm firewall policy <vmid> --in-policy ACCEPT --out-policy DROP [--node <node>]
|
|
176
|
+
proxmox vm firewall rules list <vmid> [--node <node>]
|
|
177
|
+
proxmox vm firewall rules add <vmid> --action ACCEPT --dport 22 --proto tcp [--source <cidr>] [--comment <text>]
|
|
178
|
+
proxmox vm firewall rules show <vmid> <pos>
|
|
179
|
+
proxmox vm firewall rules update <vmid> <pos> --action DROP
|
|
180
|
+
proxmox vm firewall rules delete <vmid> <pos>
|
|
181
|
+
proxmox vm firewall refs <vmid> [--type alias|ipset|group]
|
|
144
182
|
```
|
|
145
183
|
|
|
146
184
|
### Container (LXC)
|
|
@@ -152,6 +190,18 @@ proxmox container create --node <node> --vmid <id> --ostemplate <tmpl> [--memory
|
|
|
152
190
|
proxmox container start <vmid> [--node <node>]
|
|
153
191
|
proxmox container stop <vmid> [--node <node>]
|
|
154
192
|
proxmox container delete <vmid> [--node <node>] [--force] [--purge]
|
|
193
|
+
|
|
194
|
+
# Container firewall
|
|
195
|
+
proxmox container firewall options <vmid> [--node <node>]
|
|
196
|
+
proxmox container firewall enable <vmid> [--node <node>]
|
|
197
|
+
proxmox container firewall disable <vmid> [--node <node>]
|
|
198
|
+
proxmox container firewall policy <vmid> --in-policy ACCEPT --out-policy DROP
|
|
199
|
+
proxmox container firewall rules list <vmid> [--node <node>]
|
|
200
|
+
proxmox container firewall rules add <vmid> --action ACCEPT --dport 22 --proto tcp
|
|
201
|
+
proxmox container firewall rules show <vmid> <pos>
|
|
202
|
+
proxmox container firewall rules update <vmid> <pos> --action DROP
|
|
203
|
+
proxmox container firewall rules delete <vmid> <pos>
|
|
204
|
+
proxmox container firewall refs <vmid> [--type alias|ipset|group]
|
|
155
205
|
```
|
|
156
206
|
|
|
157
207
|
### Node
|
|
@@ -160,6 +210,18 @@ proxmox container delete <vmid> [--node <node>] [--force] [--purge]
|
|
|
160
210
|
proxmox node list
|
|
161
211
|
proxmox node show <node>
|
|
162
212
|
proxmox node status [<node>]
|
|
213
|
+
|
|
214
|
+
# Node firewall
|
|
215
|
+
proxmox node firewall options <node>
|
|
216
|
+
proxmox node firewall enable <node>
|
|
217
|
+
proxmox node firewall disable <node>
|
|
218
|
+
proxmox node firewall policy <node> --in-policy ACCEPT --out-policy DROP
|
|
219
|
+
proxmox node firewall rules list <node>
|
|
220
|
+
proxmox node firewall rules add <node> --action ACCEPT --dport 22 --proto tcp
|
|
221
|
+
proxmox node firewall rules show <node> <pos>
|
|
222
|
+
proxmox node firewall rules update <node> <pos> --action DROP
|
|
223
|
+
proxmox node firewall rules delete <node> <pos>
|
|
224
|
+
proxmox node firewall refs <node> [--type alias|ipset|group]
|
|
163
225
|
```
|
|
164
226
|
|
|
165
227
|
### Storage
|
|
@@ -168,12 +230,45 @@ proxmox node status [<node>]
|
|
|
168
230
|
proxmox storage list [--node <node>]
|
|
169
231
|
proxmox storage show <storage>
|
|
170
232
|
proxmox storage content <storage> [--node <node>]
|
|
233
|
+
proxmox storage upload --node <node> --storage <storage> --file <path> [--content-type iso|vztmpl|import]
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Pool
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
proxmox pool list
|
|
240
|
+
proxmox pool show <poolid>
|
|
241
|
+
proxmox pool create <poolid> [--comment <text>]
|
|
242
|
+
proxmox pool update <poolid> [--comment <text>] [--allow-delete]
|
|
243
|
+
proxmox pool delete <poolid>
|
|
171
244
|
```
|
|
172
245
|
|
|
173
246
|
### Cluster
|
|
174
247
|
|
|
175
248
|
```bash
|
|
176
249
|
proxmox cluster status
|
|
250
|
+
|
|
251
|
+
# Cluster firewall
|
|
252
|
+
proxmox cluster firewall options
|
|
253
|
+
proxmox cluster firewall enable
|
|
254
|
+
proxmox cluster firewall disable
|
|
255
|
+
proxmox cluster firewall policy --in-policy ACCEPT --out-policy DROP
|
|
256
|
+
proxmox cluster firewall rules # list (shorthand)
|
|
257
|
+
proxmox cluster firewall rules list # list (explicit)
|
|
258
|
+
proxmox cluster firewall rules add --action ACCEPT --dport 22 --source 10.0.0.0/8
|
|
259
|
+
proxmox cluster firewall rules show <pos>
|
|
260
|
+
proxmox cluster firewall rules update <pos> --action DROP
|
|
261
|
+
proxmox cluster firewall rules delete <pos>
|
|
262
|
+
proxmox cluster firewall aliases # list (shorthand)
|
|
263
|
+
proxmox cluster firewall aliases add <name> --cidr 10.0.0.0/24 --comment "web tier"
|
|
264
|
+
proxmox cluster firewall aliases delete <name>
|
|
265
|
+
proxmox cluster firewall ipsets # list (shorthand)
|
|
266
|
+
proxmox cluster firewall ipsets add <name> --comment "trusted hosts"
|
|
267
|
+
proxmox cluster firewall ipsets show <name>
|
|
268
|
+
proxmox cluster firewall ipsets delete <name>
|
|
269
|
+
proxmox cluster firewall ipsets add-cidr <name> --cidr 192.168.1.0/24
|
|
270
|
+
proxmox cluster firewall ipsets delete-cidr <name> --cidr 192.168.1.0/24
|
|
271
|
+
proxmox cluster firewall refs [--type alias|ipset|group]
|
|
177
272
|
```
|
|
178
273
|
|
|
179
274
|
### Task
|
|
@@ -260,3 +355,32 @@ uv build
|
|
|
260
355
|
## License
|
|
261
356
|
|
|
262
357
|
MIT
|
|
358
|
+
|
|
359
|
+
## Firewall Rule Options
|
|
360
|
+
|
|
361
|
+
Firewall rules share the same flags across cluster, node, VM, and container. The `--macro` flag can be used as a shortcut for common services (e.g., `--macro SSH` sets up port 22/tcp).
|
|
362
|
+
|
|
363
|
+
| Flag | Values | Description |
|
|
364
|
+
|---|---|---|
|
|
365
|
+
| `--action` | `ACCEPT`, `DENY`, `REJECT` | Rule action (required for `add`) |
|
|
366
|
+
| `--type` | `in`, `out` | Traffic direction (default: `in`) |
|
|
367
|
+
| `--iface` | e.g. `net0` | Network interface |
|
|
368
|
+
| `--source` | CIDR | Source IP/CIDR |
|
|
369
|
+
| `--dest` | CIDR | Destination IP/CIDR |
|
|
370
|
+
| `--dport` | e.g. `80` or `8000-9000` | Destination port |
|
|
371
|
+
| `--sport` | e.g. `1024-65535` | Source port |
|
|
372
|
+
| `--proto` | `tcp`, `udp`, `icmp`, `any` | Protocol |
|
|
373
|
+
| `--macro` | e.g. `SSH`, `HTTP`, `HTTPS`, `Ping` | Pre-defined service macro |
|
|
374
|
+
| `--comment` | text | Comment / description |
|
|
375
|
+
| `--enable` | `0`, `1` | Enable the rule (default: `1`) |
|
|
376
|
+
| `--log` | `emerg`..`debug`, `nolog` | Log level |
|
|
377
|
+
|
|
378
|
+
Example:
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
# Allow SSH from a specific subnet
|
|
382
|
+
proxmox vm firewall rules add 100 --action ACCEPT --dport 22 --proto tcp --source 192.168.1.0/24 --comment "Admin SSH"
|
|
383
|
+
|
|
384
|
+
# Or use a macro
|
|
385
|
+
proxmox vm firewall rules add 100 --action ACCEPT --macro SSH --source 192.168.1.0/24
|
|
386
|
+
```
|
|
@@ -28,6 +28,11 @@ proxmox auth login --url https://192.168.1.10:8006 --username root@pam --passwor
|
|
|
28
28
|
# Or with an API token
|
|
29
29
|
proxmox auth login --url https://192.168.1.10:8006 --username root@pam --api-token 'root@pam!my-token=deadbeef...'
|
|
30
30
|
|
|
31
|
+
# Enable shell completions
|
|
32
|
+
source <(proxmox completion bash) # bash
|
|
33
|
+
source <(proxmox completion zsh) # zsh
|
|
34
|
+
proxmox completion fish | source # fish (or save to ~/.config/fish/completions/proxmox.fish)
|
|
35
|
+
|
|
31
36
|
# Check auth status
|
|
32
37
|
proxmox auth status
|
|
33
38
|
|
|
@@ -106,6 +111,27 @@ proxmox auth status # Show current auth context
|
|
|
106
111
|
proxmox auth clear # Remove saved credentials
|
|
107
112
|
```
|
|
108
113
|
|
|
114
|
+
### Completion
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
proxmox completion bash # Emit bash completion script
|
|
118
|
+
proxmox completion zsh # Emit zsh completion script
|
|
119
|
+
proxmox completion fish # Emit fish completion script
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Add to your shell's rc file:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# bash (~/.bashrc)
|
|
126
|
+
source <(proxmox completion bash)
|
|
127
|
+
|
|
128
|
+
# zsh (~/.zshrc)
|
|
129
|
+
source <(proxmox completion zsh)
|
|
130
|
+
|
|
131
|
+
# fish (~/.config/fish/completions/proxmox.fish)
|
|
132
|
+
proxmox completion fish > ~/.config/fish/completions/proxmox.fish
|
|
133
|
+
```
|
|
134
|
+
|
|
109
135
|
### VM (QEMU)
|
|
110
136
|
|
|
111
137
|
```bash
|
|
@@ -118,6 +144,18 @@ proxmox vm reboot <vmid> [--node <node>]
|
|
|
118
144
|
proxmox vm suspend <vmid> [--node <node>]
|
|
119
145
|
proxmox vm resume <vmid> [--node <node>]
|
|
120
146
|
proxmox vm delete <vmid> [--node <node>] [--force] [--purge]
|
|
147
|
+
|
|
148
|
+
# VM firewall
|
|
149
|
+
proxmox vm firewall options <vmid> [--node <node>]
|
|
150
|
+
proxmox vm firewall enable <vmid> [--node <node>]
|
|
151
|
+
proxmox vm firewall disable <vmid> [--node <node>]
|
|
152
|
+
proxmox vm firewall policy <vmid> --in-policy ACCEPT --out-policy DROP [--node <node>]
|
|
153
|
+
proxmox vm firewall rules list <vmid> [--node <node>]
|
|
154
|
+
proxmox vm firewall rules add <vmid> --action ACCEPT --dport 22 --proto tcp [--source <cidr>] [--comment <text>]
|
|
155
|
+
proxmox vm firewall rules show <vmid> <pos>
|
|
156
|
+
proxmox vm firewall rules update <vmid> <pos> --action DROP
|
|
157
|
+
proxmox vm firewall rules delete <vmid> <pos>
|
|
158
|
+
proxmox vm firewall refs <vmid> [--type alias|ipset|group]
|
|
121
159
|
```
|
|
122
160
|
|
|
123
161
|
### Container (LXC)
|
|
@@ -129,6 +167,18 @@ proxmox container create --node <node> --vmid <id> --ostemplate <tmpl> [--memory
|
|
|
129
167
|
proxmox container start <vmid> [--node <node>]
|
|
130
168
|
proxmox container stop <vmid> [--node <node>]
|
|
131
169
|
proxmox container delete <vmid> [--node <node>] [--force] [--purge]
|
|
170
|
+
|
|
171
|
+
# Container firewall
|
|
172
|
+
proxmox container firewall options <vmid> [--node <node>]
|
|
173
|
+
proxmox container firewall enable <vmid> [--node <node>]
|
|
174
|
+
proxmox container firewall disable <vmid> [--node <node>]
|
|
175
|
+
proxmox container firewall policy <vmid> --in-policy ACCEPT --out-policy DROP
|
|
176
|
+
proxmox container firewall rules list <vmid> [--node <node>]
|
|
177
|
+
proxmox container firewall rules add <vmid> --action ACCEPT --dport 22 --proto tcp
|
|
178
|
+
proxmox container firewall rules show <vmid> <pos>
|
|
179
|
+
proxmox container firewall rules update <vmid> <pos> --action DROP
|
|
180
|
+
proxmox container firewall rules delete <vmid> <pos>
|
|
181
|
+
proxmox container firewall refs <vmid> [--type alias|ipset|group]
|
|
132
182
|
```
|
|
133
183
|
|
|
134
184
|
### Node
|
|
@@ -137,6 +187,18 @@ proxmox container delete <vmid> [--node <node>] [--force] [--purge]
|
|
|
137
187
|
proxmox node list
|
|
138
188
|
proxmox node show <node>
|
|
139
189
|
proxmox node status [<node>]
|
|
190
|
+
|
|
191
|
+
# Node firewall
|
|
192
|
+
proxmox node firewall options <node>
|
|
193
|
+
proxmox node firewall enable <node>
|
|
194
|
+
proxmox node firewall disable <node>
|
|
195
|
+
proxmox node firewall policy <node> --in-policy ACCEPT --out-policy DROP
|
|
196
|
+
proxmox node firewall rules list <node>
|
|
197
|
+
proxmox node firewall rules add <node> --action ACCEPT --dport 22 --proto tcp
|
|
198
|
+
proxmox node firewall rules show <node> <pos>
|
|
199
|
+
proxmox node firewall rules update <node> <pos> --action DROP
|
|
200
|
+
proxmox node firewall rules delete <node> <pos>
|
|
201
|
+
proxmox node firewall refs <node> [--type alias|ipset|group]
|
|
140
202
|
```
|
|
141
203
|
|
|
142
204
|
### Storage
|
|
@@ -145,12 +207,45 @@ proxmox node status [<node>]
|
|
|
145
207
|
proxmox storage list [--node <node>]
|
|
146
208
|
proxmox storage show <storage>
|
|
147
209
|
proxmox storage content <storage> [--node <node>]
|
|
210
|
+
proxmox storage upload --node <node> --storage <storage> --file <path> [--content-type iso|vztmpl|import]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Pool
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
proxmox pool list
|
|
217
|
+
proxmox pool show <poolid>
|
|
218
|
+
proxmox pool create <poolid> [--comment <text>]
|
|
219
|
+
proxmox pool update <poolid> [--comment <text>] [--allow-delete]
|
|
220
|
+
proxmox pool delete <poolid>
|
|
148
221
|
```
|
|
149
222
|
|
|
150
223
|
### Cluster
|
|
151
224
|
|
|
152
225
|
```bash
|
|
153
226
|
proxmox cluster status
|
|
227
|
+
|
|
228
|
+
# Cluster firewall
|
|
229
|
+
proxmox cluster firewall options
|
|
230
|
+
proxmox cluster firewall enable
|
|
231
|
+
proxmox cluster firewall disable
|
|
232
|
+
proxmox cluster firewall policy --in-policy ACCEPT --out-policy DROP
|
|
233
|
+
proxmox cluster firewall rules # list (shorthand)
|
|
234
|
+
proxmox cluster firewall rules list # list (explicit)
|
|
235
|
+
proxmox cluster firewall rules add --action ACCEPT --dport 22 --source 10.0.0.0/8
|
|
236
|
+
proxmox cluster firewall rules show <pos>
|
|
237
|
+
proxmox cluster firewall rules update <pos> --action DROP
|
|
238
|
+
proxmox cluster firewall rules delete <pos>
|
|
239
|
+
proxmox cluster firewall aliases # list (shorthand)
|
|
240
|
+
proxmox cluster firewall aliases add <name> --cidr 10.0.0.0/24 --comment "web tier"
|
|
241
|
+
proxmox cluster firewall aliases delete <name>
|
|
242
|
+
proxmox cluster firewall ipsets # list (shorthand)
|
|
243
|
+
proxmox cluster firewall ipsets add <name> --comment "trusted hosts"
|
|
244
|
+
proxmox cluster firewall ipsets show <name>
|
|
245
|
+
proxmox cluster firewall ipsets delete <name>
|
|
246
|
+
proxmox cluster firewall ipsets add-cidr <name> --cidr 192.168.1.0/24
|
|
247
|
+
proxmox cluster firewall ipsets delete-cidr <name> --cidr 192.168.1.0/24
|
|
248
|
+
proxmox cluster firewall refs [--type alias|ipset|group]
|
|
154
249
|
```
|
|
155
250
|
|
|
156
251
|
### Task
|
|
@@ -237,3 +332,32 @@ uv build
|
|
|
237
332
|
## License
|
|
238
333
|
|
|
239
334
|
MIT
|
|
335
|
+
|
|
336
|
+
## Firewall Rule Options
|
|
337
|
+
|
|
338
|
+
Firewall rules share the same flags across cluster, node, VM, and container. The `--macro` flag can be used as a shortcut for common services (e.g., `--macro SSH` sets up port 22/tcp).
|
|
339
|
+
|
|
340
|
+
| Flag | Values | Description |
|
|
341
|
+
|---|---|---|
|
|
342
|
+
| `--action` | `ACCEPT`, `DENY`, `REJECT` | Rule action (required for `add`) |
|
|
343
|
+
| `--type` | `in`, `out` | Traffic direction (default: `in`) |
|
|
344
|
+
| `--iface` | e.g. `net0` | Network interface |
|
|
345
|
+
| `--source` | CIDR | Source IP/CIDR |
|
|
346
|
+
| `--dest` | CIDR | Destination IP/CIDR |
|
|
347
|
+
| `--dport` | e.g. `80` or `8000-9000` | Destination port |
|
|
348
|
+
| `--sport` | e.g. `1024-65535` | Source port |
|
|
349
|
+
| `--proto` | `tcp`, `udp`, `icmp`, `any` | Protocol |
|
|
350
|
+
| `--macro` | e.g. `SSH`, `HTTP`, `HTTPS`, `Ping` | Pre-defined service macro |
|
|
351
|
+
| `--comment` | text | Comment / description |
|
|
352
|
+
| `--enable` | `0`, `1` | Enable the rule (default: `1`) |
|
|
353
|
+
| `--log` | `emerg`..`debug`, `nolog` | Log level |
|
|
354
|
+
|
|
355
|
+
Example:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
# Allow SSH from a specific subnet
|
|
359
|
+
proxmox vm firewall rules add 100 --action ACCEPT --dport 22 --proto tcp --source 192.168.1.0/24 --comment "Admin SSH"
|
|
360
|
+
|
|
361
|
+
# Or use a macro
|
|
362
|
+
proxmox vm firewall rules add 100 --action ACCEPT --macro SSH --source 192.168.1.0/24
|
|
363
|
+
```
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Shell completion script generation for bash, zsh, and fish."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import textwrap
|
|
7
|
+
|
|
8
|
+
# Each resource has a known list of actions and optional flag completions.
|
|
9
|
+
# We introspect from the parser instead of hardcoding, keeping this DRY.
|
|
10
|
+
|
|
11
|
+
BASH_SCRIPT = textwrap.dedent("""\
|
|
12
|
+
_proxmox_completion() {{
|
|
13
|
+
local cur prev words cword
|
|
14
|
+
_init_completion || return
|
|
15
|
+
|
|
16
|
+
# Global flags
|
|
17
|
+
local global_flags="--url --username --password --password-stdin --api-token --output --dry-run --insecure --timeout --verbose --version --help"
|
|
18
|
+
|
|
19
|
+
# Resources and their actions (autogenerated)
|
|
20
|
+
declare -A _proxmox_resources
|
|
21
|
+
{resource_map}
|
|
22
|
+
|
|
23
|
+
# If we're at the first positional (after global flags), complete resources
|
|
24
|
+
if [[ $cword -eq 1 ]] || [[ "${{words[$((cword-1))]}}" =~ ^(--.*) ]]; then
|
|
25
|
+
COMPREPLY=($(compgen -W "${{!_proxmox_resources[*]}}" -- "$cur"))
|
|
26
|
+
return
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Find the resource word
|
|
30
|
+
local resource=""
|
|
31
|
+
for w in "${{words[@]:1}}"; do
|
|
32
|
+
if [[ -n "${{_proxmox_resources[$w]}}" ]]; then
|
|
33
|
+
resource="$w"
|
|
34
|
+
break
|
|
35
|
+
fi
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
if [[ -n "$resource" ]]; then
|
|
39
|
+
local actions="${{_proxmox_resources[$resource]}}"
|
|
40
|
+
# Complete actions
|
|
41
|
+
COMPREPLY=($(compgen -W "$actions" -- "$cur"))
|
|
42
|
+
fi
|
|
43
|
+
}}
|
|
44
|
+
|
|
45
|
+
complete -F _proxmox_completion proxmox
|
|
46
|
+
""")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
ZSH_SCRIPT = textwrap.dedent("""\
|
|
50
|
+
#compdef proxmox
|
|
51
|
+
|
|
52
|
+
_proxmox() {{
|
|
53
|
+
local -a resources
|
|
54
|
+
resources=({resources})
|
|
55
|
+
|
|
56
|
+
local -A resource_actions
|
|
57
|
+
{resource_zsh_map}
|
|
58
|
+
|
|
59
|
+
local curcontext="$curcontext" state line
|
|
60
|
+
typeset -A opt_args
|
|
61
|
+
|
|
62
|
+
_arguments -C \\
|
|
63
|
+
{resource_zsh_flags} \\
|
|
64
|
+
'1:resource:($resources)' \\
|
|
65
|
+
'*::arg:->args'
|
|
66
|
+
|
|
67
|
+
case $state in
|
|
68
|
+
args)
|
|
69
|
+
local resource="$line[1]"
|
|
70
|
+
if [[ -n "${{resource_actions[$resource]}}" ]]; then
|
|
71
|
+
_arguments "*:action:(${{resource_actions[$resource]}})"
|
|
72
|
+
fi
|
|
73
|
+
;;
|
|
74
|
+
esac
|
|
75
|
+
}}
|
|
76
|
+
|
|
77
|
+
_proxmox "$@"
|
|
78
|
+
""")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
FISH_SCRIPT = textwrap.dedent("""\
|
|
82
|
+
function __fish_proxmox_resources
|
|
83
|
+
echo {resources}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
function __fish_proxmox_actions
|
|
87
|
+
set -l resource (commandline -opc)[1]
|
|
88
|
+
switch $resource
|
|
89
|
+
{fish_cases}
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Global flags
|
|
94
|
+
complete -c proxmox -f
|
|
95
|
+
complete -c proxmox -l url -d 'Proxmox API URL'
|
|
96
|
+
complete -c proxmox -l username -d 'Username'
|
|
97
|
+
complete -c proxmox -l password -d 'Password'
|
|
98
|
+
complete -c proxmox -l password-stdin -d 'Read password from stdin'
|
|
99
|
+
complete -c proxmox -l api-token -d 'API token'
|
|
100
|
+
complete -c proxmox -l output -a 'json table yaml' -d 'Output format'
|
|
101
|
+
complete -c proxmox -l dry-run -d 'Print request without executing'
|
|
102
|
+
complete -c proxmox -l insecure -d 'Skip TLS verification'
|
|
103
|
+
complete -c proxmox -l timeout -d 'Request timeout in seconds'
|
|
104
|
+
complete -c proxmox -l verbose -d 'Enable debug output'
|
|
105
|
+
complete -c proxmox -l version -d 'Show version'
|
|
106
|
+
complete -c proxmox -l help -d 'Show help'
|
|
107
|
+
|
|
108
|
+
# Resource completion at position 1
|
|
109
|
+
complete -c proxmox -n 'not __fish_seen_subcommand_from {resources}' \\
|
|
110
|
+
-a '(__fish_proxmox_resources)'
|
|
111
|
+
|
|
112
|
+
# Action completion per resource
|
|
113
|
+
{fish_action_completions}
|
|
114
|
+
""")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _collect_parser_tree(parser: argparse.ArgumentParser) -> dict[str, list[str]]:
|
|
118
|
+
"""Walk the root parser and collect resource -> action mapping."""
|
|
119
|
+
resource_actions: dict[str, list[str]] = {}
|
|
120
|
+
|
|
121
|
+
for action in parser._actions:
|
|
122
|
+
if not isinstance(action, argparse._SubParsersAction):
|
|
123
|
+
continue
|
|
124
|
+
for name, sub_parser in action.choices.items():
|
|
125
|
+
sub_actions: list[str] = []
|
|
126
|
+
for sa in sub_parser._actions:
|
|
127
|
+
if isinstance(sa, argparse._SubParsersAction):
|
|
128
|
+
sub_actions.extend(sa.choices.keys())
|
|
129
|
+
resource_actions[name] = sorted(sub_actions)
|
|
130
|
+
|
|
131
|
+
return resource_actions
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def generate_bash(parser: argparse.ArgumentParser) -> str:
|
|
135
|
+
"""Generate a bash completion script."""
|
|
136
|
+
resources = _collect_parser_tree(parser)
|
|
137
|
+
lines = []
|
|
138
|
+
for name, actions in sorted(resources.items()):
|
|
139
|
+
lines.append(f' _proxmox_resources[{name}]="{ " ".join(actions) }"')
|
|
140
|
+
return BASH_SCRIPT.format(resource_map="\n".join(lines))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def generate_zsh(parser: argparse.ArgumentParser) -> str:
|
|
144
|
+
"""Generate a zsh completion script."""
|
|
145
|
+
resources = _collect_parser_tree(parser)
|
|
146
|
+
resources_list = " ".join(sorted(resources.keys()))
|
|
147
|
+
|
|
148
|
+
zsh_lines = []
|
|
149
|
+
for name, actions in sorted(resources.items()):
|
|
150
|
+
zsh_lines.append(f' resource_actions[{name}]="{ " ".join(actions) }"')
|
|
151
|
+
|
|
152
|
+
return ZSH_SCRIPT.format(
|
|
153
|
+
resources=resources_list,
|
|
154
|
+
resource_zsh_map="\n".join(zsh_lines),
|
|
155
|
+
resource_zsh_flags="",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def generate_fish(parser: argparse.ArgumentParser) -> str:
|
|
160
|
+
"""Generate a fish completion script."""
|
|
161
|
+
resources = _collect_parser_tree(parser)
|
|
162
|
+
resources_list = " ".join(sorted(resources.keys()))
|
|
163
|
+
|
|
164
|
+
fish_cases = []
|
|
165
|
+
for name, actions in sorted(resources.items()):
|
|
166
|
+
fish_cases.append(f" case {name}\n echo {' '.join(actions)}")
|
|
167
|
+
|
|
168
|
+
fish_completions = []
|
|
169
|
+
for name, actions in sorted(resources.items()):
|
|
170
|
+
fish_completions.append(
|
|
171
|
+
f"complete -c proxmox -n '__fish_seen_subcommand_from {name}' "
|
|
172
|
+
f"-a '{' '.join(actions)}'"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return FISH_SCRIPT.format(
|
|
176
|
+
resources=resources_list,
|
|
177
|
+
fish_cases="\n".join(fish_cases),
|
|
178
|
+
fish_action_completions="\n".join(fish_completions),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
# CLI registration
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def register_completion_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
188
|
+
"""Register the `proxmox completion` subcommand."""
|
|
189
|
+
comp_parser = subparsers.add_parser(
|
|
190
|
+
"completion", help="Generate shell completion script"
|
|
191
|
+
)
|
|
192
|
+
comp_parser.add_argument(
|
|
193
|
+
"shell",
|
|
194
|
+
choices=["bash", "zsh", "fish"],
|
|
195
|
+
help="Target shell",
|
|
196
|
+
)
|
|
197
|
+
comp_parser.set_defaults(func=_emit_completion)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _emit_completion(args: argparse.Namespace, _client: object) -> str:
|
|
201
|
+
"""Emit the completion script for the requested shell.
|
|
202
|
+
|
|
203
|
+
We need access to the root parser to introspect the subcommand tree,
|
|
204
|
+
so we import the builder here.
|
|
205
|
+
"""
|
|
206
|
+
from proxmox.cli.main import build_root_parser
|
|
207
|
+
|
|
208
|
+
parser = build_root_parser()
|
|
209
|
+
generators = {
|
|
210
|
+
"bash": generate_bash,
|
|
211
|
+
"zsh": generate_zsh,
|
|
212
|
+
"fish": generate_fish,
|
|
213
|
+
}
|
|
214
|
+
return generators[args.shell](parser)
|
|
@@ -57,8 +57,10 @@ def build_root_parser() -> argparse.ArgumentParser:
|
|
|
57
57
|
# Import and register subcommands
|
|
58
58
|
from proxmox.cli.auth import register_auth_parser
|
|
59
59
|
from proxmox.cli.cluster import register_cluster_parser
|
|
60
|
+
from proxmox.cli.completion import register_completion_parser
|
|
60
61
|
from proxmox.cli.container import register_container_parser
|
|
61
62
|
from proxmox.cli.node import register_node_parser
|
|
63
|
+
from proxmox.cli.pool import register_pool_parser
|
|
62
64
|
from proxmox.cli.storage import register_storage_parser
|
|
63
65
|
from proxmox.cli.tasks import register_task_parser
|
|
64
66
|
from proxmox.cli.vm import register_vm_parser
|
|
@@ -66,9 +68,11 @@ def build_root_parser() -> argparse.ArgumentParser:
|
|
|
66
68
|
register_auth_parser(subparsers)
|
|
67
69
|
register_vm_parser(subparsers)
|
|
68
70
|
register_node_parser(subparsers)
|
|
71
|
+
register_pool_parser(subparsers)
|
|
69
72
|
register_container_parser(subparsers)
|
|
70
73
|
register_storage_parser(subparsers)
|
|
71
74
|
register_cluster_parser(subparsers)
|
|
75
|
+
register_completion_parser(subparsers)
|
|
72
76
|
register_task_parser(subparsers)
|
|
73
77
|
|
|
74
78
|
return parser
|
|
@@ -202,13 +206,17 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
202
206
|
return
|
|
203
207
|
|
|
204
208
|
try:
|
|
205
|
-
# auth status and
|
|
206
|
-
if args.resource == "auth" and args.action in ("status", "clear"):
|
|
209
|
+
# auth status, clear, and completion don't need a client
|
|
210
|
+
if (args.resource == "auth" and args.action in ("status", "clear")) or args.resource == "completion":
|
|
207
211
|
if hasattr(args, "func"):
|
|
208
212
|
result = args.func(args, None)
|
|
209
213
|
if result is not None:
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
if args.resource == "completion":
|
|
215
|
+
# Completion scripts are raw shell code, not JSON
|
|
216
|
+
print(result)
|
|
217
|
+
else:
|
|
218
|
+
output = format_output(result, args.output)
|
|
219
|
+
print(output)
|
|
212
220
|
return
|
|
213
221
|
|
|
214
222
|
_, overrides = _merge_config(args)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""`proxmox pool` subcommand — resource pool management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from proxmox.client.client import ProxmoxClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register_pool_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
11
|
+
"""Register the `proxmox pool` subcommand tree."""
|
|
12
|
+
pool_parser = subparsers.add_parser("pool", help="Manage resource pools")
|
|
13
|
+
pool_sub = pool_parser.add_subparsers(dest="action", title="actions", required=True)
|
|
14
|
+
|
|
15
|
+
# --- pool list ---
|
|
16
|
+
pool_list = pool_sub.add_parser("list", help="List all pools")
|
|
17
|
+
pool_list.set_defaults(func=_pool_list)
|
|
18
|
+
|
|
19
|
+
# --- pool show ---
|
|
20
|
+
pool_show = pool_sub.add_parser("show", help="Show pool details and members")
|
|
21
|
+
pool_show.add_argument("poolid", help="Pool ID")
|
|
22
|
+
pool_show.set_defaults(func=_pool_show)
|
|
23
|
+
|
|
24
|
+
# --- pool create ---
|
|
25
|
+
pool_create = pool_sub.add_parser("create", help="Create a new pool")
|
|
26
|
+
pool_create.add_argument("poolid", help="Pool ID")
|
|
27
|
+
pool_create.add_argument("--comment", default=None, help="Pool description / comment")
|
|
28
|
+
pool_create.set_defaults(func=_pool_create)
|
|
29
|
+
|
|
30
|
+
# --- pool update ---
|
|
31
|
+
pool_update = pool_sub.add_parser("update", help="Update a pool's comment")
|
|
32
|
+
pool_update.add_argument("poolid", help="Pool ID")
|
|
33
|
+
pool_update.add_argument("--comment", default=None, help="New comment (omit to clear)")
|
|
34
|
+
pool_update.add_argument("--allow-delete", action="store_true", help="Allow deletion of pool comment")
|
|
35
|
+
pool_update.set_defaults(func=_pool_update)
|
|
36
|
+
|
|
37
|
+
# --- pool delete ---
|
|
38
|
+
pool_delete = pool_sub.add_parser("delete", help="Delete a pool")
|
|
39
|
+
pool_delete.add_argument("poolid", help="Pool ID")
|
|
40
|
+
pool_delete.set_defaults(func=_pool_delete)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Handlers
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
def _pool_list(_args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
48
|
+
return client.get("/pools")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _pool_show(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
52
|
+
return client.get(f"/pools/{args.poolid}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _pool_create(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
56
|
+
data: dict = {"poolid": args.poolid}
|
|
57
|
+
if args.comment:
|
|
58
|
+
data["comment"] = args.comment
|
|
59
|
+
return client.post("/pools", data=data)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _pool_update(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
63
|
+
data: dict = {}
|
|
64
|
+
if args.comment is not None:
|
|
65
|
+
data["comment"] = args.comment
|
|
66
|
+
if args.allow_delete:
|
|
67
|
+
data["delete"] = 1
|
|
68
|
+
if not data:
|
|
69
|
+
return {"error": "Nothing to update. Use --comment or --allow-delete."}
|
|
70
|
+
return client.put(f"/pools/{args.poolid}", data=data)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _pool_delete(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
74
|
+
return client.delete(f"/pools/{args.poolid}")
|
|
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
|