proxctl 0.2.2__tar.gz → 0.2.4__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.2 → proxctl-0.2.4}/CHANGELOG.md +10 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/Cargo.lock +1 -1
- {proxctl-0.2.2 → proxctl-0.2.4}/Cargo.toml +1 -1
- {proxctl-0.2.2 → proxctl-0.2.4}/PKG-INFO +1 -1
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/firewall.rs +83 -211
- {proxctl-0.2.2 → proxctl-0.2.4}/src/main.rs +54 -5
- {proxctl-0.2.2 → proxctl-0.2.4}/.github/workflows/ci.yml +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/.github/workflows/release.yml +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/.gitignore +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/LICENSE +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/Makefile +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/README.md +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/prek.toml +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/proxctl_py/__init__.py +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/proxctl_py/__main__.py +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/pyproject.toml +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/api/client.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/api/error.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/api/mod.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/api/token.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/api/types.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/access.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/api.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/container.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/diff.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/firewall.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/manifest.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/mod.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/reconciler.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/vm.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/backup.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/ceph.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/cluster.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/config.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/firewall.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/lifecycle.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/migrate.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/mod.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/snapshot.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/export.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/mod.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/node.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/pool.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/storage.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/task.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/agent.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/cloudinit.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/config.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/firewall.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/lifecycle.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/migrate.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/mod.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/snapshot.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/lib.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/output.rs +0 -0
- {proxctl-0.2.2 → proxctl-0.2.4}/src/schema.rs +0 -0
|
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## [0.2.4](https://github.com/rvben/proxctl/compare/v0.2.3...v0.2.4) - 2026-04-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **init**: add API docs URL, colored output, and next steps to config init ([53e3ca9](https://github.com/rvben/proxctl/commit/53e3ca91dfd0f448205afb8da4902ace2c98880f))
|
|
14
|
+
|
|
15
|
+
## [0.2.3](https://github.com/rvben/proxctl/compare/v0.2.2...v0.2.3) - 2026-04-03
|
|
16
|
+
|
|
7
17
|
## [0.2.2](https://github.com/rvben/proxctl/compare/v0.2.1...v0.2.2) - 2026-04-03
|
|
8
18
|
|
|
9
19
|
### Added
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use clap::Subcommand;
|
|
1
|
+
use clap::{Args, Subcommand};
|
|
2
2
|
use owo_colors::OwoColorize;
|
|
3
3
|
use serde_json::json;
|
|
4
4
|
|
|
@@ -6,6 +6,40 @@ use crate::api::Error;
|
|
|
6
6
|
use crate::api::client::ProxmoxClient;
|
|
7
7
|
use crate::output::{OutputConfig, use_color};
|
|
8
8
|
|
|
9
|
+
#[derive(Args)]
|
|
10
|
+
pub struct FirewallRuleArgs {
|
|
11
|
+
/// Rule action (ACCEPT, DROP, REJECT)
|
|
12
|
+
#[arg(long)]
|
|
13
|
+
pub action: String,
|
|
14
|
+
/// Rule type (in, out, group)
|
|
15
|
+
#[arg(long, rename_all = "kebab-case")]
|
|
16
|
+
pub r#type: String,
|
|
17
|
+
/// Enable the rule
|
|
18
|
+
#[arg(long)]
|
|
19
|
+
pub enable: Option<bool>,
|
|
20
|
+
/// Network interface (e.g. vmbr0, vmbr0v30)
|
|
21
|
+
#[arg(long)]
|
|
22
|
+
pub iface: Option<String>,
|
|
23
|
+
/// Source address
|
|
24
|
+
#[arg(long)]
|
|
25
|
+
pub source: Option<String>,
|
|
26
|
+
/// Destination address
|
|
27
|
+
#[arg(long)]
|
|
28
|
+
pub dest: Option<String>,
|
|
29
|
+
/// Destination port
|
|
30
|
+
#[arg(long)]
|
|
31
|
+
pub dport: Option<String>,
|
|
32
|
+
/// Protocol
|
|
33
|
+
#[arg(long)]
|
|
34
|
+
pub proto: Option<String>,
|
|
35
|
+
/// Macro (e.g. SSH, HTTP, HTTPS)
|
|
36
|
+
#[arg(long, rename_all = "kebab-case")]
|
|
37
|
+
pub r#macro: Option<String>,
|
|
38
|
+
/// Comment
|
|
39
|
+
#[arg(long)]
|
|
40
|
+
pub comment: Option<String>,
|
|
41
|
+
}
|
|
42
|
+
|
|
9
43
|
fn require_node<'a>(node: Option<&'a str>, global_node: Option<&'a str>) -> Result<&'a str, Error> {
|
|
10
44
|
node.or(global_node)
|
|
11
45
|
.ok_or_else(|| Error::Config("node name required (use --node or PROXMOX_NODE)".to_string()))
|
|
@@ -33,36 +67,8 @@ pub enum ClusterFirewallCommand {
|
|
|
33
67
|
Rules,
|
|
34
68
|
/// Add a cluster firewall rule
|
|
35
69
|
Add {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
action: String,
|
|
39
|
-
/// Rule type (in, out, group)
|
|
40
|
-
#[arg(long, rename_all = "kebab-case")]
|
|
41
|
-
r#type: String,
|
|
42
|
-
/// Enable the rule
|
|
43
|
-
#[arg(long)]
|
|
44
|
-
enable: Option<bool>,
|
|
45
|
-
/// Network interface (e.g. vmbr0, vmbr0v30)
|
|
46
|
-
#[arg(long)]
|
|
47
|
-
iface: Option<String>,
|
|
48
|
-
/// Source address
|
|
49
|
-
#[arg(long)]
|
|
50
|
-
source: Option<String>,
|
|
51
|
-
/// Destination address
|
|
52
|
-
#[arg(long)]
|
|
53
|
-
dest: Option<String>,
|
|
54
|
-
/// Destination port
|
|
55
|
-
#[arg(long)]
|
|
56
|
-
dport: Option<String>,
|
|
57
|
-
/// Protocol
|
|
58
|
-
#[arg(long)]
|
|
59
|
-
proto: Option<String>,
|
|
60
|
-
/// Macro (e.g. SSH, HTTP, HTTPS)
|
|
61
|
-
#[arg(long, rename_all = "kebab-case")]
|
|
62
|
-
r#macro: Option<String>,
|
|
63
|
-
/// Comment
|
|
64
|
-
#[arg(long)]
|
|
65
|
-
comment: Option<String>,
|
|
70
|
+
#[command(flatten)]
|
|
71
|
+
rule: Box<FirewallRuleArgs>,
|
|
66
72
|
},
|
|
67
73
|
/// Delete a cluster firewall rule
|
|
68
74
|
Delete {
|
|
@@ -88,36 +94,8 @@ pub enum NodeFirewallCommand {
|
|
|
88
94
|
/// Node name
|
|
89
95
|
#[arg(long)]
|
|
90
96
|
node: Option<String>,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
action: String,
|
|
94
|
-
/// Rule type (in, out, group)
|
|
95
|
-
#[arg(long, rename_all = "kebab-case")]
|
|
96
|
-
r#type: String,
|
|
97
|
-
/// Enable the rule
|
|
98
|
-
#[arg(long)]
|
|
99
|
-
enable: Option<bool>,
|
|
100
|
-
/// Network interface (e.g. vmbr0, vmbr0v30)
|
|
101
|
-
#[arg(long)]
|
|
102
|
-
iface: Option<String>,
|
|
103
|
-
/// Source address
|
|
104
|
-
#[arg(long)]
|
|
105
|
-
source: Option<String>,
|
|
106
|
-
/// Destination address
|
|
107
|
-
#[arg(long)]
|
|
108
|
-
dest: Option<String>,
|
|
109
|
-
/// Destination port
|
|
110
|
-
#[arg(long)]
|
|
111
|
-
dport: Option<String>,
|
|
112
|
-
/// Protocol
|
|
113
|
-
#[arg(long)]
|
|
114
|
-
proto: Option<String>,
|
|
115
|
-
/// Macro (e.g. SSH, HTTP, HTTPS)
|
|
116
|
-
#[arg(long, rename_all = "kebab-case")]
|
|
117
|
-
r#macro: Option<String>,
|
|
118
|
-
/// Comment
|
|
119
|
-
#[arg(long)]
|
|
120
|
-
comment: Option<String>,
|
|
97
|
+
#[command(flatten)]
|
|
98
|
+
rule: Box<FirewallRuleArgs>,
|
|
121
99
|
},
|
|
122
100
|
/// Delete a node firewall rule
|
|
123
101
|
Delete {
|
|
@@ -214,33 +192,19 @@ pub async fn run(
|
|
|
214
192
|
match cmd {
|
|
215
193
|
FirewallCommand::Cluster(sub) => match sub {
|
|
216
194
|
ClusterFirewallCommand::Rules => cluster_rules(client, out).await,
|
|
217
|
-
ClusterFirewallCommand::Add {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
client,
|
|
231
|
-
out,
|
|
232
|
-
&action,
|
|
233
|
-
&r#type,
|
|
234
|
-
enable,
|
|
235
|
-
iface.as_deref(),
|
|
236
|
-
source.as_deref(),
|
|
237
|
-
dest.as_deref(),
|
|
238
|
-
dport.as_deref(),
|
|
239
|
-
proto.as_deref(),
|
|
240
|
-
r#macro.as_deref(),
|
|
241
|
-
comment.as_deref(),
|
|
242
|
-
)
|
|
243
|
-
.await
|
|
195
|
+
ClusterFirewallCommand::Add { rule } => {
|
|
196
|
+
let params = build_rule_params(&rule);
|
|
197
|
+
let param_refs: Vec<(&str, &str)> = params
|
|
198
|
+
.iter()
|
|
199
|
+
.map(|(k, v)| (k.as_str(), v.as_str()))
|
|
200
|
+
.collect();
|
|
201
|
+
let _: serde_json::Value =
|
|
202
|
+
client.post("/cluster/firewall/rules", ¶m_refs).await?;
|
|
203
|
+
out.print_result(
|
|
204
|
+
&json!({"status": "rule added", "scope": "cluster"}),
|
|
205
|
+
"Cluster firewall rule added",
|
|
206
|
+
);
|
|
207
|
+
Ok(())
|
|
244
208
|
}
|
|
245
209
|
ClusterFirewallCommand::Delete { pos, yes } => {
|
|
246
210
|
cluster_delete_rule(client, out, pos, yes).await
|
|
@@ -251,36 +215,20 @@ pub async fn run(
|
|
|
251
215
|
let n = require_node(node.as_deref(), global_node)?;
|
|
252
216
|
node_rules(client, out, n).await
|
|
253
217
|
}
|
|
254
|
-
NodeFirewallCommand::Add {
|
|
255
|
-
node,
|
|
256
|
-
action,
|
|
257
|
-
r#type,
|
|
258
|
-
enable,
|
|
259
|
-
iface,
|
|
260
|
-
source,
|
|
261
|
-
dest,
|
|
262
|
-
dport,
|
|
263
|
-
proto,
|
|
264
|
-
r#macro,
|
|
265
|
-
comment,
|
|
266
|
-
} => {
|
|
218
|
+
NodeFirewallCommand::Add { node, rule } => {
|
|
267
219
|
let n = require_node(node.as_deref(), global_node)?;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
r#macro.as_deref(),
|
|
281
|
-
comment.as_deref(),
|
|
282
|
-
)
|
|
283
|
-
.await
|
|
220
|
+
let params = build_rule_params(&rule);
|
|
221
|
+
let param_refs: Vec<(&str, &str)> = params
|
|
222
|
+
.iter()
|
|
223
|
+
.map(|(k, v)| (k.as_str(), v.as_str()))
|
|
224
|
+
.collect();
|
|
225
|
+
let path = format!("/nodes/{n}/firewall/rules");
|
|
226
|
+
let _: serde_json::Value = client.post(&path, ¶m_refs).await?;
|
|
227
|
+
out.print_result(
|
|
228
|
+
&json!({"status": "rule added", "scope": "node", "node": n}),
|
|
229
|
+
&format!("Node {n} firewall rule added"),
|
|
230
|
+
);
|
|
231
|
+
Ok(())
|
|
284
232
|
}
|
|
285
233
|
NodeFirewallCommand::Delete { node, pos, yes } => {
|
|
286
234
|
let n = require_node(node.as_deref(), global_node)?;
|
|
@@ -351,46 +299,34 @@ fn print_rules(data: &[serde_json::Value]) {
|
|
|
351
299
|
}
|
|
352
300
|
}
|
|
353
301
|
|
|
354
|
-
|
|
355
|
-
fn build_rule_params(
|
|
356
|
-
action: &str,
|
|
357
|
-
rule_type: &str,
|
|
358
|
-
enable: Option<bool>,
|
|
359
|
-
iface: Option<&str>,
|
|
360
|
-
source: Option<&str>,
|
|
361
|
-
dest: Option<&str>,
|
|
362
|
-
dport: Option<&str>,
|
|
363
|
-
proto: Option<&str>,
|
|
364
|
-
r#macro: Option<&str>,
|
|
365
|
-
comment: Option<&str>,
|
|
366
|
-
) -> Vec<(String, String)> {
|
|
302
|
+
fn build_rule_params(rule: &FirewallRuleArgs) -> Vec<(String, String)> {
|
|
367
303
|
let mut params: Vec<(String, String)> = vec![
|
|
368
|
-
("action".to_string(), action.
|
|
369
|
-
("type".to_string(),
|
|
304
|
+
("action".to_string(), rule.action.clone()),
|
|
305
|
+
("type".to_string(), rule.r#type.clone()),
|
|
370
306
|
];
|
|
371
|
-
if let Some(e) = enable {
|
|
307
|
+
if let Some(e) = rule.enable {
|
|
372
308
|
params.push(("enable".to_string(), if e { "1" } else { "0" }.to_string()));
|
|
373
309
|
}
|
|
374
|
-
if let Some(i) = iface {
|
|
375
|
-
params.push(("iface".to_string(), i.
|
|
310
|
+
if let Some(ref i) = rule.iface {
|
|
311
|
+
params.push(("iface".to_string(), i.clone()));
|
|
376
312
|
}
|
|
377
|
-
if let Some(s) = source {
|
|
378
|
-
params.push(("source".to_string(), s.
|
|
313
|
+
if let Some(ref s) = rule.source {
|
|
314
|
+
params.push(("source".to_string(), s.clone()));
|
|
379
315
|
}
|
|
380
|
-
if let Some(d) = dest {
|
|
381
|
-
params.push(("dest".to_string(), d.
|
|
316
|
+
if let Some(ref d) = rule.dest {
|
|
317
|
+
params.push(("dest".to_string(), d.clone()));
|
|
382
318
|
}
|
|
383
|
-
if let Some(dp) = dport {
|
|
384
|
-
params.push(("dport".to_string(), dp.
|
|
319
|
+
if let Some(ref dp) = rule.dport {
|
|
320
|
+
params.push(("dport".to_string(), dp.clone()));
|
|
385
321
|
}
|
|
386
|
-
if let Some(p) = proto {
|
|
387
|
-
params.push(("proto".to_string(), p.
|
|
322
|
+
if let Some(ref p) = rule.proto {
|
|
323
|
+
params.push(("proto".to_string(), p.clone()));
|
|
388
324
|
}
|
|
389
|
-
if let Some(m) = r#macro {
|
|
390
|
-
params.push(("macro".to_string(), m.
|
|
325
|
+
if let Some(ref m) = rule.r#macro {
|
|
326
|
+
params.push(("macro".to_string(), m.clone()));
|
|
391
327
|
}
|
|
392
|
-
if let Some(c) = comment {
|
|
393
|
-
params.push(("comment".to_string(), c.
|
|
328
|
+
if let Some(ref c) = rule.comment {
|
|
329
|
+
params.push(("comment".to_string(), c.clone()));
|
|
394
330
|
}
|
|
395
331
|
params
|
|
396
332
|
}
|
|
@@ -412,37 +348,6 @@ async fn cluster_rules(client: &ProxmoxClient, out: OutputConfig) -> Result<(),
|
|
|
412
348
|
Ok(())
|
|
413
349
|
}
|
|
414
350
|
|
|
415
|
-
#[allow(clippy::too_many_arguments)]
|
|
416
|
-
async fn cluster_add_rule(
|
|
417
|
-
client: &ProxmoxClient,
|
|
418
|
-
out: OutputConfig,
|
|
419
|
-
action: &str,
|
|
420
|
-
rule_type: &str,
|
|
421
|
-
enable: Option<bool>,
|
|
422
|
-
iface: Option<&str>,
|
|
423
|
-
source: Option<&str>,
|
|
424
|
-
dest: Option<&str>,
|
|
425
|
-
dport: Option<&str>,
|
|
426
|
-
proto: Option<&str>,
|
|
427
|
-
r#macro: Option<&str>,
|
|
428
|
-
comment: Option<&str>,
|
|
429
|
-
) -> Result<(), Error> {
|
|
430
|
-
let params = build_rule_params(
|
|
431
|
-
action, rule_type, enable, iface, source, dest, dport, proto, r#macro, comment,
|
|
432
|
-
);
|
|
433
|
-
let param_refs: Vec<(&str, &str)> = params
|
|
434
|
-
.iter()
|
|
435
|
-
.map(|(k, v)| (k.as_str(), v.as_str()))
|
|
436
|
-
.collect();
|
|
437
|
-
let _: serde_json::Value = client.post("/cluster/firewall/rules", ¶m_refs).await?;
|
|
438
|
-
|
|
439
|
-
out.print_result(
|
|
440
|
-
&json!({"status": "rule added", "scope": "cluster"}),
|
|
441
|
-
"Cluster firewall rule added",
|
|
442
|
-
);
|
|
443
|
-
Ok(())
|
|
444
|
-
}
|
|
445
|
-
|
|
446
351
|
async fn cluster_delete_rule(
|
|
447
352
|
client: &ProxmoxClient,
|
|
448
353
|
out: OutputConfig,
|
|
@@ -481,39 +386,6 @@ async fn node_rules(client: &ProxmoxClient, out: OutputConfig, node: &str) -> Re
|
|
|
481
386
|
Ok(())
|
|
482
387
|
}
|
|
483
388
|
|
|
484
|
-
#[allow(clippy::too_many_arguments)]
|
|
485
|
-
async fn node_add_rule(
|
|
486
|
-
client: &ProxmoxClient,
|
|
487
|
-
out: OutputConfig,
|
|
488
|
-
node: &str,
|
|
489
|
-
action: &str,
|
|
490
|
-
rule_type: &str,
|
|
491
|
-
enable: Option<bool>,
|
|
492
|
-
iface: Option<&str>,
|
|
493
|
-
source: Option<&str>,
|
|
494
|
-
dest: Option<&str>,
|
|
495
|
-
dport: Option<&str>,
|
|
496
|
-
proto: Option<&str>,
|
|
497
|
-
r#macro: Option<&str>,
|
|
498
|
-
comment: Option<&str>,
|
|
499
|
-
) -> Result<(), Error> {
|
|
500
|
-
let params = build_rule_params(
|
|
501
|
-
action, rule_type, enable, iface, source, dest, dport, proto, r#macro, comment,
|
|
502
|
-
);
|
|
503
|
-
let param_refs: Vec<(&str, &str)> = params
|
|
504
|
-
.iter()
|
|
505
|
-
.map(|(k, v)| (k.as_str(), v.as_str()))
|
|
506
|
-
.collect();
|
|
507
|
-
let path = format!("/nodes/{node}/firewall/rules");
|
|
508
|
-
let _: serde_json::Value = client.post(&path, ¶m_refs).await?;
|
|
509
|
-
|
|
510
|
-
out.print_result(
|
|
511
|
-
&json!({"status": "rule added", "scope": "node", "node": node}),
|
|
512
|
-
&format!("Node {node} firewall rule added"),
|
|
513
|
-
);
|
|
514
|
-
Ok(())
|
|
515
|
-
}
|
|
516
|
-
|
|
517
389
|
async fn node_delete_rule(
|
|
518
390
|
client: &ProxmoxClient,
|
|
519
391
|
out: OutputConfig,
|
|
@@ -537,6 +537,24 @@ fn normalize_host(host: &str) -> String {
|
|
|
537
537
|
}
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
+
fn sym_ok() -> String {
|
|
541
|
+
use owo_colors::OwoColorize;
|
|
542
|
+
if proxctl::output::use_color() {
|
|
543
|
+
"✔".green().to_string()
|
|
544
|
+
} else {
|
|
545
|
+
"✔".to_owned()
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
fn sym_dim(s: &str) -> String {
|
|
550
|
+
use owo_colors::OwoColorize;
|
|
551
|
+
if proxctl::output::use_color() {
|
|
552
|
+
s.dimmed().to_string()
|
|
553
|
+
} else {
|
|
554
|
+
s.to_owned()
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
540
558
|
async fn run_config_init() -> Result<(), Error> {
|
|
541
559
|
use dialoguer::{Confirm, Input};
|
|
542
560
|
|
|
@@ -589,7 +607,9 @@ async fn run_config_init() -> Result<(), Error> {
|
|
|
589
607
|
.map_err(|e| Error::Other(format!("failed to read password: {e}")))?;
|
|
590
608
|
|
|
591
609
|
// Step 6: Verify credentials — POST to /api2/json/access/ticket
|
|
592
|
-
|
|
610
|
+
let sep = sym_dim("──────────────");
|
|
611
|
+
eprintln!();
|
|
612
|
+
eprintln!(" Connecting to {base_url} ...");
|
|
593
613
|
|
|
594
614
|
let http = reqwest::Client::builder()
|
|
595
615
|
.danger_accept_invalid_certs(insecure)
|
|
@@ -627,7 +647,16 @@ async fn run_config_init() -> Result<(), Error> {
|
|
|
627
647
|
.ok_or_else(|| Error::Auth("ticket response missing data.CSRFPreventionToken".to_string()))?
|
|
628
648
|
.to_string();
|
|
629
649
|
|
|
630
|
-
|
|
650
|
+
eprintln!(" {} Authenticated", sym_ok());
|
|
651
|
+
eprintln!();
|
|
652
|
+
eprintln!(
|
|
653
|
+
" {}",
|
|
654
|
+
sym_dim(&format!(
|
|
655
|
+
"API docs: {base_url}/pve-docs/api-viewer/index.html"
|
|
656
|
+
))
|
|
657
|
+
);
|
|
658
|
+
eprintln!();
|
|
659
|
+
eprintln!(" Creating API token ...");
|
|
631
660
|
|
|
632
661
|
// Step 7: Create API token
|
|
633
662
|
let token_id = "proxctl";
|
|
@@ -644,7 +673,7 @@ async fn run_config_init() -> Result<(), Error> {
|
|
|
644
673
|
|
|
645
674
|
let token_json: serde_json::Value = if create_resp.status().as_u16() == 400 {
|
|
646
675
|
// Token already exists — delete it and recreate
|
|
647
|
-
|
|
676
|
+
eprintln!(" Token already exists, recreating ...");
|
|
648
677
|
|
|
649
678
|
let del_resp = http
|
|
650
679
|
.delete(&token_url)
|
|
@@ -705,8 +734,28 @@ async fn run_config_init() -> Result<(), Error> {
|
|
|
705
734
|
insecure,
|
|
706
735
|
)?;
|
|
707
736
|
|
|
708
|
-
|
|
709
|
-
|
|
737
|
+
eprintln!();
|
|
738
|
+
eprintln!(
|
|
739
|
+
" {} Configuration saved to {}",
|
|
740
|
+
sym_ok(),
|
|
741
|
+
config_path.display()
|
|
742
|
+
);
|
|
743
|
+
eprintln!();
|
|
744
|
+
eprintln!("{sep}");
|
|
745
|
+
eprintln!(" Next steps:");
|
|
746
|
+
eprintln!(
|
|
747
|
+
" {}",
|
|
748
|
+
sym_dim("proxctl health # verify connectivity")
|
|
749
|
+
);
|
|
750
|
+
eprintln!(
|
|
751
|
+
" {}",
|
|
752
|
+
sym_dim("proxctl vm list # list virtual machines")
|
|
753
|
+
);
|
|
754
|
+
eprintln!(
|
|
755
|
+
" {}",
|
|
756
|
+
sym_dim("proxctl completions zsh # shell completions")
|
|
757
|
+
);
|
|
758
|
+
eprintln!("{sep}");
|
|
710
759
|
|
|
711
760
|
Ok(())
|
|
712
761
|
}
|
|
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
|