]>
Commit | Line | Data |
---|---|---|
0b9614d5 | 1 | use std::fmt::Write; |
b0ef9631 HL |
2 | use std::path::Path; |
3 | use std::process::Command; | |
4 | ||
5736fa91 TL |
5 | fn get_top_processes() -> String { |
6 | let (exe, args) = ("top", vec!["-b", "-c", "-w512", "-n", "1", "-o", "TIME"]); | |
7 | let output = Command::new(exe) | |
8 | .args(&args) | |
9 | .output(); | |
10 | let output = match output { | |
11 | Ok(output) => String::from_utf8_lossy(&output.stdout).to_string(), | |
12 | Err(err) => err.to_string(), | |
13 | }; | |
14 | let output = output.lines().take(30).collect::<Vec<&str>>().join("\n"); | |
15 | format!("$ `{exe} {}`\n```\n{output}\n```", args.join(" ")) | |
16 | } | |
17 | ||
eb32373e | 18 | fn files() -> Vec<(&'static str, Vec<&'static str>)> { |
93f077c5 | 19 | vec![ |
eb32373e | 20 | ( |
2f7b3e2e TL |
21 | "General System Info", |
22 | vec![ | |
23 | "/etc/hostname", | |
24 | "/etc/hosts", | |
25 | "/etc/network/interfaces", | |
26 | "/etc/apt/sources.list", | |
27 | "/etc/apt/sources.list.d/", | |
49d465c7 | 28 | "/proc/pressure/", |
2f7b3e2e | 29 | ], |
eb32373e TL |
30 | ), |
31 | ( | |
32 | "Datastores & Remotes", | |
33 | vec!["/etc/proxmox-backup/datastore.cfg"], | |
34 | ), | |
35 | ( | |
36 | "User & Access", | |
37 | vec![ | |
38 | "/etc/proxmox-backup/user.cfg", | |
39 | "/etc/proxmox-backup/acl.cfg", | |
40 | ], | |
41 | ), | |
42 | ("Remotes", vec!["/etc/proxmox-backup/remote.cfg"]), | |
43 | ( | |
44 | "Jobs", | |
45 | vec![ | |
46 | "/etc/proxmox-backup/sync.cfg", | |
47 | "/etc/proxmox-backup/verification.cfg", | |
48 | ], | |
49 | ), | |
50 | ( | |
51 | "Tape", | |
52 | vec![ | |
53 | "/etc/proxmox-backup/tape.cfg", | |
54 | "/etc/proxmox-backup/media-pool.cfg", | |
55 | ], | |
56 | ), | |
57 | ( | |
58 | "Others", | |
59 | vec![ | |
60 | "/etc/proxmox-backup/node.cfg", | |
61 | "/etc/proxmox-backup/traffic-control.cfg", | |
62 | ], | |
63 | ), | |
93f077c5 TL |
64 | ] |
65 | } | |
b0ef9631 | 66 | |
93f077c5 TL |
67 | fn commands() -> Vec<(&'static str, Vec<&'static str>)> { |
68 | vec![ | |
ee0ea735 | 69 | // ("<command>", vec![<arg [, arg]>]) |
a1a9fdd8 | 70 | ("date", vec!["-R"]), |
c100fe91 | 71 | ("proxmox-backup-manager", vec!["versions", "--verbose"]), |
b2fc573a | 72 | ("proxmox-backup-manager", vec!["subscription", "get"]), |
11c4632b TL |
73 | ("proxmox-backup-manager", vec!["ldap", "list"]), |
74 | ("proxmox-backup-manager", vec!["openid", "list"]), | |
3b5cb8fd | 75 | ("proxmox-boot-tool", vec!["status"]), |
93f077c5 | 76 | ("df", vec!["-h"]), |
84fb190e TL |
77 | ( |
78 | "lsblk", | |
79 | vec![ | |
80 | "--ascii", | |
81 | "-M", | |
82 | "-o", | |
83 | "+HOTPLUG,ROTA,PHY-SEC,FSTYPE,MODEL,TRAN", | |
84 | ], | |
85 | ), | |
bb9e5039 | 86 | ("ls", vec!["-l", "/dev/disk/by-id", "/dev/disk/by-path"]), |
2f08ee1f TL |
87 | ("zpool", vec!["status"]), |
88 | ("zfs", vec!["list"]), | |
fcc8e354 | 89 | ("arcstat", vec![]), |
93f077c5 TL |
90 | ] |
91 | } | |
b0ef9631 | 92 | |
432fe441 FG |
93 | // (description, function()) |
94 | type FunctionMapping = (&'static str, fn() -> String); | |
95 | ||
96 | fn function_calls() -> Vec<FunctionMapping> { | |
49d465c7 TL |
97 | vec![ |
98 | ("Datastores", || { | |
99 | let config = match pbs_config::datastore::config() { | |
100 | Ok((config, _digest)) => config, | |
101 | _ => return String::from("could not read datastore config"), | |
102 | }; | |
b0ef9631 | 103 | |
49d465c7 TL |
104 | let mut list = Vec::new(); |
105 | for store in config.sections.keys() { | |
106 | list.push(store.as_str()); | |
107 | } | |
108 | list.join(", ") | |
109 | }), | |
5736fa91 | 110 | ("System Load & Uptime", get_top_processes), |
49d465c7 | 111 | ] |
b0ef9631 HL |
112 | } |
113 | ||
0b9614d5 | 114 | fn get_file_content(file: impl AsRef<Path>) -> String { |
25877d05 | 115 | use proxmox_sys::fs::file_read_optional_string; |
0b9614d5 TL |
116 | let content = match file_read_optional_string(&file) { |
117 | Ok(Some(content)) => content, | |
118 | Ok(None) => String::from("# file does not exist"), | |
119 | Err(err) => err.to_string(), | |
120 | }; | |
121 | let file_name = file.as_ref().display(); | |
122 | format!("`$ cat '{file_name}'`\n```\n{}\n```", content.trim_end()) | |
123 | } | |
124 | ||
125 | fn get_directory_content(path: impl AsRef<Path>) -> String { | |
126 | let read_dir_iter = match std::fs::read_dir(&path) { | |
127 | Ok(iter) => iter, | |
128 | Err(err) => { | |
129 | return format!( | |
130 | "`$ cat '{}*'`\n```\n# read dir failed - {}\n```", | |
131 | path.as_ref().display(), | |
132 | err.to_string(), | |
133 | ); | |
134 | } | |
135 | }; | |
136 | let mut out = String::new(); | |
460c3d16 | 137 | let mut first = true; |
0b9614d5 TL |
138 | for entry in read_dir_iter { |
139 | let entry = match entry { | |
140 | Ok(entry) => entry, | |
141 | Err(err) => { | |
142 | let _ = writeln!(out, "error during read-dir - {}", err.to_string()); | |
143 | continue; | |
144 | } | |
145 | }; | |
146 | let path = entry.path(); | |
147 | if path.is_file() { | |
460c3d16 TL |
148 | if first { |
149 | let _ = writeln!(out, "{}", get_file_content(path)); | |
150 | first = false; | |
151 | } else { | |
152 | let _ = writeln!(out, "\n{}", get_file_content(path)); | |
153 | } | |
0b9614d5 TL |
154 | } else { |
155 | let _ = writeln!(out, "skipping sub-directory `{}`", path.display()); | |
156 | } | |
157 | } | |
158 | out | |
159 | } | |
b0ef9631 | 160 | |
c55884d1 TL |
161 | fn get_command_output(exe: &str, args: &Vec<&str>) -> String { |
162 | let output = Command::new(exe) | |
163 | .env("PROXMOX_OUTPUT_NO_BORDER", "1") | |
164 | .args(args) | |
165 | .output(); | |
166 | let output = match output { | |
12217941 TL |
167 | Ok(output) => { |
168 | let mut out = String::from_utf8_lossy(&output.stdout) | |
169 | .trim_end() | |
170 | .to_string(); | |
171 | let stderr = String::from_utf8_lossy(&output.stderr) | |
172 | .trim_end() | |
173 | .to_string(); | |
174 | if !stderr.is_empty() { | |
164f96a5 | 175 | let _ = writeln!(out, "\n```\nSTDERR:\n```\n{stderr}"); |
12217941 TL |
176 | } |
177 | out | |
178 | } | |
c55884d1 TL |
179 | Err(err) => err.to_string(), |
180 | }; | |
c55884d1 TL |
181 | format!("$ `{exe} {}`\n```\n{output}\n```", args.join(" ")) |
182 | } | |
183 | ||
0b9614d5 | 184 | pub fn generate_report() -> String { |
93f077c5 | 185 | let file_contents = files() |
b0ef9631 | 186 | .iter() |
eb32373e TL |
187 | .map(|group| { |
188 | let (group, files) = group; | |
189 | let group_content = files | |
190 | .iter() | |
191 | .map(|file_name| { | |
0b9614d5 TL |
192 | let path = Path::new(file_name); |
193 | if path.is_dir() { | |
194 | get_directory_content(&path) | |
195 | } else { | |
196 | get_file_content(file_name) | |
197 | } | |
eb32373e TL |
198 | }) |
199 | .collect::<Vec<String>>() | |
200 | .join("\n\n"); | |
201 | ||
202 | format!("### {group}\n\n{group_content}") | |
b0ef9631 HL |
203 | }) |
204 | .collect::<Vec<String>>() | |
205 | .join("\n\n"); | |
206 | ||
93f077c5 | 207 | let command_outputs = commands() |
b0ef9631 | 208 | .iter() |
c55884d1 | 209 | .map(|(command, args)| get_command_output(command, args)) |
b0ef9631 HL |
210 | .collect::<Vec<String>>() |
211 | .join("\n\n"); | |
212 | ||
93f077c5 | 213 | let function_outputs = function_calls() |
b0ef9631 | 214 | .iter() |
20875de2 TL |
215 | .map(|(desc, function)| { |
216 | let output = function(); | |
217 | format!("#### {desc}\n```\n{}\n```", output.trim_end()) | |
218 | }) | |
b0ef9631 HL |
219 | .collect::<Vec<String>>() |
220 | .join("\n\n"); | |
221 | ||
222 | format!( | |
20875de2 | 223 | "## FILES\n\n{file_contents}\n## COMMANDS\n\n{command_outputs}\n## FUNCTIONS\n\n{function_outputs}\n" |
b0ef9631 HL |
224 | ) |
225 | } |