max / mountaineer
4 files changed,
+167 insertions,
-9 deletions
| @@ -6,6 +6,11 @@ | |||
| 6 | 6 | # | |
| 7 | 7 | # Order matters: roles are presented in the list view in the order they | |
| 8 | 8 | # appear here. Group by concern (core → network → graphical). | |
| 9 | + | # | |
| 10 | + | # Optional fields: | |
| 11 | + | # profile = "server" | "sysadmin" — defaults to both builds if absent | |
| 12 | + | # version_cmd = ["bin", "--flag"] — argv used by `sysop info <role>` | |
| 13 | + | # to display a version string | |
| 9 | 14 | ||
| 10 | 15 | # --- core --- | |
| 11 | 16 | ||
| @@ -29,6 +34,7 @@ description = "Login interactive shell with structured pipelines" | |||
| 29 | 34 | tool = "nushell" | |
| 30 | 35 | packages = ["nushell"] | |
| 31 | 36 | config_paths = ["~/.config/nushell/"] | |
| 37 | + | version_cmd = ["nu", "--version"] | |
| 32 | 38 | ||
| 33 | 39 | [[role]] | |
| 34 | 40 | name = "editor" | |
| @@ -36,6 +42,7 @@ description = "Modal editor with LSP and tree-sitter" | |||
| 36 | 42 | tool = "helix" | |
| 37 | 43 | packages = ["helix"] | |
| 38 | 44 | config_paths = ["~/.config/helix/"] | |
| 45 | + | version_cmd = ["hx", "--version"] | |
| 39 | 46 | ||
| 40 | 47 | [[role]] | |
| 41 | 48 | name = "multiplexer" | |
| @@ -43,6 +50,7 @@ description = "Terminal multiplexer with discoverable keybindings" | |||
| 43 | 50 | tool = "zellij" | |
| 44 | 51 | packages = ["zellij"] | |
| 45 | 52 | config_paths = ["~/.config/zellij/"] | |
| 53 | + | version_cmd = ["zellij", "--version"] | |
| 46 | 54 | ||
| 47 | 55 | [[role]] | |
| 48 | 56 | name = "container-runtime" | |
| @@ -50,6 +58,7 @@ description = "Rootless container runtime" | |||
| 50 | 58 | tool = "podman" | |
| 51 | 59 | packages = ["podman"] | |
| 52 | 60 | config_paths = ["/etc/containers/", "~/.config/containers/"] | |
| 61 | + | version_cmd = ["podman", "--version"] | |
| 53 | 62 | ||
| 54 | 63 | [[role]] | |
| 55 | 64 | name = "filesystem" | |
| @@ -57,6 +66,7 @@ description = "Root filesystem and pool/dataset management" | |||
| 57 | 66 | tool = "ZFS" | |
| 58 | 67 | packages = ["zfs", "zfs-lts"] | |
| 59 | 68 | config_paths = ["/etc/zfs/"] | |
| 69 | + | version_cmd = ["zfs", "--version"] | |
| 60 | 70 | ||
| 61 | 71 | [[role]] | |
| 62 | 72 | name = "ssh" | |
| @@ -64,6 +74,7 @@ description = "SSH server, hardened defaults" | |||
| 64 | 74 | tool = "OpenSSH" | |
| 65 | 75 | packages = ["openssh"] | |
| 66 | 76 | config_paths = ["/etc/ssh/sshd_config", "/etc/ssh/sshd_config.d/"] | |
| 77 | + | version_cmd = ["ssh", "-V"] | |
| 67 | 78 | ||
| 68 | 79 | # --- network --- | |
| 69 | 80 | ||
| @@ -73,6 +84,7 @@ description = "Stateful packet filter, default-deny" | |||
| 73 | 84 | tool = "nftables" | |
| 74 | 85 | packages = ["nftables"] | |
| 75 | 86 | config_paths = ["/etc/nftables.nft", "/etc/nftables.d/"] | |
| 87 | + | version_cmd = ["nft", "--version"] | |
| 76 | 88 | ||
| 77 | 89 | [[role]] | |
| 78 | 90 | name = "resolver" | |
| @@ -80,6 +92,7 @@ description = "Local validating recursive DNS resolver" | |||
| 80 | 92 | tool = "unbound" | |
| 81 | 93 | packages = ["unbound"] | |
| 82 | 94 | config_paths = ["/etc/unbound/unbound.conf", "/etc/unbound/conf.d/"] | |
| 95 | + | version_cmd = ["unbound", "-V"] | |
| 83 | 96 | ||
| 84 | 97 | [[role]] | |
| 85 | 98 | name = "time" | |
| @@ -87,6 +100,7 @@ description = "NTP client and time synchronisation" | |||
| 87 | 100 | tool = "chrony" | |
| 88 | 101 | packages = ["chrony"] | |
| 89 | 102 | config_paths = ["/etc/chrony/chrony.conf"] | |
| 103 | + | version_cmd = ["chronyd", "-v"] | |
| 90 | 104 | ||
| 91 | 105 | [[role]] | |
| 92 | 106 | name = "network-wired" | |
| @@ -108,6 +122,7 @@ description = "Overlay mesh VPN with mutual auth" | |||
| 108 | 122 | tool = "Nebula" | |
| 109 | 123 | packages = ["nebula"] | |
| 110 | 124 | config_paths = ["/etc/nebula/"] | |
| 125 | + | version_cmd = ["nebula", "-version"] | |
| 111 | 126 | ||
| 112 | 127 | [[role]] | |
| 113 | 128 | name = "reverse-proxy" | |
| @@ -115,6 +130,7 @@ description = "HTTPS-terminating reverse proxy with auto-TLS" | |||
| 115 | 130 | tool = "Caddy" | |
| 116 | 131 | packages = ["caddy"] | |
| 117 | 132 | config_paths = ["/etc/caddy/Caddyfile", "/etc/caddy/conf.d/"] | |
| 133 | + | version_cmd = ["caddy", "version"] | |
| 118 | 134 | ||
| 119 | 135 | [[role]] | |
| 120 | 136 | name = "mail-outbound" | |
| @@ -122,6 +138,7 @@ description = "Outbound mail relay (notifications, alerts)" | |||
| 122 | 138 | tool = "msmtp" | |
| 123 | 139 | packages = ["msmtp"] | |
| 124 | 140 | config_paths = ["/etc/msmtprc"] | |
| 141 | + | version_cmd = ["msmtp", "--version"] | |
| 125 | 142 | ||
| 126 | 143 | # --- observability / data --- | |
| 127 | 144 | ||
| @@ -131,6 +148,7 @@ description = "Structured log collection and routing" | |||
| 131 | 148 | tool = "Vector" | |
| 132 | 149 | packages = ["vector"] | |
| 133 | 150 | config_paths = ["/etc/vector/vector.toml"] | |
| 151 | + | version_cmd = ["vector", "--version"] | |
| 134 | 152 | ||
| 135 | 153 | [[role]] | |
| 136 | 154 | name = "logs-viewer" | |
| @@ -138,6 +156,7 @@ description = "Interactive log browser" | |||
| 138 | 156 | tool = "lnav" | |
| 139 | 157 | packages = ["lnav"] | |
| 140 | 158 | config_paths = ["~/.config/lnav/"] | |
| 159 | + | version_cmd = ["lnav", "-V"] | |
| 141 | 160 | ||
| 142 | 161 | [[role]] | |
| 143 | 162 | name = "secrets" | |
| @@ -145,6 +164,7 @@ description = "Age-encrypted secrets with sops integration" | |||
| 145 | 164 | tool = "age + sops" | |
| 146 | 165 | packages = ["age", "sops"] | |
| 147 | 166 | config_paths = ["~/.config/sops/", ".sops.yaml"] | |
| 167 | + | version_cmd = ["age", "--version"] | |
| 148 | 168 | ||
| 149 | 169 | [[role]] | |
| 150 | 170 | name = "backup" | |
| @@ -152,6 +172,7 @@ description = "Encrypted, deduplicated backups" | |||
| 152 | 172 | tool = "restic" | |
| 153 | 173 | packages = ["restic"] | |
| 154 | 174 | config_paths = ["~/.config/restic/"] | |
| 175 | + | version_cmd = ["restic", "version"] | |
| 155 | 176 | ||
| 156 | 177 | # --- graphical layer (Sysadmin build only) --- | |
| 157 | 178 | ||
| @@ -161,6 +182,8 @@ description = "Tiling Wayland compositor" | |||
| 161 | 182 | tool = "river" | |
| 162 | 183 | packages = ["river"] | |
| 163 | 184 | config_paths = ["~/.config/river/init"] | |
| 185 | + | profile = "sysadmin" | |
| 186 | + | version_cmd = ["river", "-version"] | |
| 164 | 187 | ||
| 165 | 188 | [[role]] | |
| 166 | 189 | name = "terminal" | |
| @@ -168,6 +191,8 @@ description = "Wayland terminal emulator" | |||
| 168 | 191 | tool = "foot" | |
| 169 | 192 | packages = ["foot"] | |
| 170 | 193 | config_paths = ["~/.config/foot/foot.ini"] | |
| 194 | + | profile = "sysadmin" | |
| 195 | + | version_cmd = ["foot", "--version"] | |
| 171 | 196 | ||
| 172 | 197 | [[role]] | |
| 173 | 198 | name = "bar" | |
| @@ -175,6 +200,8 @@ description = "Status bar (read-only system surface)" | |||
| 175 | 200 | tool = "yambar" | |
| 176 | 201 | packages = ["yambar"] | |
| 177 | 202 | config_paths = ["~/.config/yambar/config.yml"] | |
| 203 | + | profile = "sysadmin" | |
| 204 | + | version_cmd = ["yambar", "--version"] | |
| 178 | 205 | ||
| 179 | 206 | [[role]] | |
| 180 | 207 | name = "launcher" | |
| @@ -182,6 +209,8 @@ description = "Application launcher" | |||
| 182 | 209 | tool = "fuzzel" | |
| 183 | 210 | packages = ["fuzzel"] | |
| 184 | 211 | config_paths = ["~/.config/fuzzel/fuzzel.ini"] | |
| 212 | + | profile = "sysadmin" | |
| 213 | + | version_cmd = ["fuzzel", "--version"] | |
| 185 | 214 | ||
| 186 | 215 | [[role]] | |
| 187 | 216 | name = "clipboard" | |
| @@ -189,6 +218,7 @@ description = "Clipboard history" | |||
| 189 | 218 | tool = "cliphist" | |
| 190 | 219 | packages = ["cliphist"] | |
| 191 | 220 | config_paths = [] | |
| 221 | + | profile = "sysadmin" | |
| 192 | 222 | ||
| 193 | 223 | [[role]] | |
| 194 | 224 | name = "notifications" | |
| @@ -196,6 +226,8 @@ description = "Wayland notification daemon" | |||
| 196 | 226 | tool = "fnott" | |
| 197 | 227 | packages = ["fnott"] | |
| 198 | 228 | config_paths = ["~/.config/fnott/fnott.ini"] | |
| 229 | + | profile = "sysadmin" | |
| 230 | + | version_cmd = ["fnott", "--version"] | |
| 199 | 231 | ||
| 200 | 232 | [[role]] | |
| 201 | 233 | name = "screenshots" | |
| @@ -203,6 +235,7 @@ description = "Region capture, annotation, pipeline" | |||
| 203 | 235 | tool = "grim + slurp + swappy" | |
| 204 | 236 | packages = ["grim", "slurp", "swappy"] | |
| 205 | 237 | config_paths = ["~/.config/swappy/"] | |
| 238 | + | profile = "sysadmin" | |
| 206 | 239 | ||
| 207 | 240 | [[role]] | |
| 208 | 241 | name = "lock" | |
| @@ -210,6 +243,8 @@ description = "Screen locker" | |||
| 210 | 243 | tool = "swaylock" | |
| 211 | 244 | packages = ["swaylock"] | |
| 212 | 245 | config_paths = ["~/.config/swaylock/config"] | |
| 246 | + | profile = "sysadmin" | |
| 247 | + | version_cmd = ["swaylock", "--version"] | |
| 213 | 248 | ||
| 214 | 249 | [[role]] | |
| 215 | 250 | name = "idle" | |
| @@ -217,6 +252,8 @@ description = "Idle event dispatcher" | |||
| 217 | 252 | tool = "swayidle" | |
| 218 | 253 | packages = ["swayidle"] | |
| 219 | 254 | config_paths = ["~/.config/swayidle/config"] | |
| 255 | + | profile = "sysadmin" | |
| 256 | + | version_cmd = ["swayidle", "-v"] | |
| 220 | 257 | ||
| 221 | 258 | [[role]] | |
| 222 | 259 | name = "wallpaper" | |
| @@ -224,6 +261,7 @@ description = "Wallpaper / background" | |||
| 224 | 261 | tool = "swaybg" | |
| 225 | 262 | packages = ["swaybg"] | |
| 226 | 263 | config_paths = [] | |
| 264 | + | profile = "sysadmin" | |
| 227 | 265 | ||
| 228 | 266 | [[role]] | |
| 229 | 267 | name = "audio" | |
| @@ -231,6 +269,8 @@ description = "Audio server and session manager" | |||
| 231 | 269 | tool = "pipewire + wireplumber" | |
| 232 | 270 | packages = ["pipewire", "wireplumber"] | |
| 233 | 271 | config_paths = ["/etc/pipewire/", "~/.config/pipewire/", "~/.config/wireplumber/"] | |
| 272 | + | profile = "sysadmin" | |
| 273 | + | version_cmd = ["pipewire", "--version"] | |
| 234 | 274 | ||
| 235 | 275 | [[role]] | |
| 236 | 276 | name = "seat" | |
| @@ -238,6 +278,7 @@ description = "Seat / device management for non-root Wayland" | |||
| 238 | 278 | tool = "seatd" | |
| 239 | 279 | packages = ["seatd"] | |
| 240 | 280 | config_paths = [] | |
| 281 | + | profile = "sysadmin" | |
| 241 | 282 | ||
| 242 | 283 | [[role]] | |
| 243 | 284 | name = "brightness" | |
| @@ -245,6 +286,8 @@ description = "Backlight control" | |||
| 245 | 286 | tool = "brightnessctl" | |
| 246 | 287 | packages = ["brightnessctl"] | |
| 247 | 288 | config_paths = [] | |
| 289 | + | profile = "sysadmin" | |
| 290 | + | version_cmd = ["brightnessctl", "--version"] | |
| 248 | 291 | ||
| 249 | 292 | [[role]] | |
| 250 | 293 | name = "power" | |
| @@ -252,3 +295,4 @@ description = "Laptop power management profiles" | |||
| 252 | 295 | tool = "tlp" | |
| 253 | 296 | packages = ["tlp"] | |
| 254 | 297 | config_paths = ["/etc/tlp.conf", "/etc/tlp.d/"] | |
| 298 | + | profile = "sysadmin" |
| @@ -1,4 +1,4 @@ | |||
| 1 | - | use crate::roles::{self, Role}; | |
| 1 | + | use crate::roles::{self, Profile, Role}; | |
| 2 | 2 | use anyhow::Result; | |
| 3 | 3 | use crossterm::event::{self, Event, KeyCode}; | |
| 4 | 4 | use ratatui::Frame; | |
| @@ -6,6 +6,8 @@ use ratatui::layout::{Constraint, Layout, Rect}; | |||
| 6 | 6 | use ratatui::style::Style; | |
| 7 | 7 | use ratatui::text::{Line, Span}; | |
| 8 | 8 | use ratatui::widgets::Paragraph; | |
| 9 | + | use std::collections::HashMap; | |
| 10 | + | use std::process::Command; | |
| 9 | 11 | use sysop_tui::{Footer, GlobalAction, classify, hint, layout, palette, selection, style}; | |
| 10 | 12 | ||
| 11 | 13 | enum View { | |
| @@ -13,8 +15,17 @@ enum View { | |||
| 13 | 15 | Detail { idx: usize }, | |
| 14 | 16 | } | |
| 15 | 17 | ||
| 16 | - | pub fn run(initial: Option<String>) -> Result<i32> { | |
| 17 | - | let roles = roles::load()?; | |
| 18 | + | pub fn run(initial: Option<String>, profile: Option<Profile>) -> Result<i32> { | |
| 19 | + | let all_roles = roles::load()?; | |
| 20 | + | let roles: Vec<Role> = all_roles | |
| 21 | + | .into_iter() | |
| 22 | + | .filter(|r| r.included_in(profile)) | |
| 23 | + | .collect(); | |
| 24 | + | ||
| 25 | + | if roles.is_empty() { | |
| 26 | + | eprintln!("sysop info: no roles match the requested profile"); | |
| 27 | + | return Ok(1); | |
| 28 | + | } | |
| 18 | 29 | ||
| 19 | 30 | let mut view = match initial.as_deref() { | |
| 20 | 31 | None => View::List { selected: 0 }, | |
| @@ -30,8 +41,10 @@ pub fn run(initial: Option<String>) -> Result<i32> { | |||
| 30 | 41 | }, | |
| 31 | 42 | }; | |
| 32 | 43 | ||
| 44 | + | let mut versions: HashMap<String, Option<String>> = HashMap::new(); | |
| 45 | + | ||
| 33 | 46 | let mut terminal = ratatui::init(); | |
| 34 | - | let result = event_loop(&mut terminal, &roles, &mut view); | |
| 47 | + | let result = event_loop(&mut terminal, &roles, &mut view, &mut versions); | |
| 35 | 48 | ratatui::restore(); | |
| 36 | 49 | result.map(|_| 0) | |
| 37 | 50 | } | |
| @@ -40,9 +53,14 @@ fn event_loop( | |||
| 40 | 53 | terminal: &mut ratatui::DefaultTerminal, | |
| 41 | 54 | roles: &[Role], | |
| 42 | 55 | view: &mut View, | |
| 56 | + | versions: &mut HashMap<String, Option<String>>, | |
| 43 | 57 | ) -> Result<()> { | |
| 44 | 58 | loop { | |
| 45 | - | terminal.draw(|frame| draw(frame, roles, view))?; | |
| 59 | + | if let View::Detail { idx } = view { | |
| 60 | + | ensure_version(&roles[*idx], versions); | |
| 61 | + | } | |
| 62 | + | ||
| 63 | + | terminal.draw(|frame| draw(frame, roles, view, versions))?; | |
| 46 | 64 | ||
| 47 | 65 | let Event::Key(key) = event::read()? else { | |
| 48 | 66 | continue; | |
| @@ -86,7 +104,41 @@ fn event_loop( | |||
| 86 | 104 | } | |
| 87 | 105 | } | |
| 88 | 106 | ||
| 89 | - | fn draw(frame: &mut Frame, roles: &[Role], view: &View) { | |
| 107 | + | fn ensure_version(role: &Role, versions: &mut HashMap<String, Option<String>>) { | |
| 108 | + | if versions.contains_key(&role.name) { | |
| 109 | + | return; | |
| 110 | + | } | |
| 111 | + | versions.insert(role.name.clone(), query_version(role)); | |
| 112 | + | } | |
| 113 | + | ||
| 114 | + | fn query_version(role: &Role) -> Option<String> { | |
| 115 | + | let argv = role.version_cmd.as_ref()?; | |
| 116 | + | let (bin, args) = argv.split_first()?; | |
| 117 | + | let output = Command::new(bin) | |
| 118 | + | .args(args) | |
| 119 | + | .output() | |
| 120 | + | .ok()?; | |
| 121 | + | let stdout = String::from_utf8_lossy(&output.stdout); | |
| 122 | + | let stderr = String::from_utf8_lossy(&output.stderr); | |
| 123 | + | let combined = if stdout.trim().is_empty() { | |
| 124 | + | stderr | |
| 125 | + | } else { | |
| 126 | + | stdout | |
| 127 | + | }; | |
| 128 | + | let first = combined.lines().next()?.trim(); | |
| 129 | + | if first.is_empty() { | |
| 130 | + | None | |
| 131 | + | } else { | |
| 132 | + | Some(first.to_string()) | |
| 133 | + | } | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | fn draw( | |
| 137 | + | frame: &mut Frame, | |
| 138 | + | roles: &[Role], | |
| 139 | + | view: &View, | |
| 140 | + | versions: &HashMap<String, Option<String>>, | |
| 141 | + | ) { | |
| 90 | 142 | let [body, foot] = layout::split(frame.area()); | |
| 91 | 143 | match view { | |
| 92 | 144 | View::List { selected } => { | |
| @@ -101,7 +153,7 @@ fn draw(frame: &mut Frame, roles: &[Role], view: &View) { | |||
| 101 | 153 | ); | |
| 102 | 154 | } | |
| 103 | 155 | View::Detail { idx } => { | |
| 104 | - | draw_detail(frame, &roles[*idx], body); | |
| 156 | + | draw_detail(frame, &roles[*idx], versions.get(&roles[*idx].name), body); | |
| 105 | 157 | frame.render_widget( | |
| 106 | 158 | &Footer::new([ | |
| 107 | 159 | hint("j/k", "next/prev role"), | |
| @@ -138,7 +190,12 @@ fn list_widget(roles: &[Role], selected: usize) -> Paragraph<'_> { | |||
| 138 | 190 | Paragraph::new(lines).style(Style::default().bg(palette::BG).fg(palette::FG)) | |
| 139 | 191 | } | |
| 140 | 192 | ||
| 141 | - | fn draw_detail(frame: &mut Frame, role: &Role, area: Rect) { | |
| 193 | + | fn draw_detail( | |
| 194 | + | frame: &mut Frame, | |
| 195 | + | role: &Role, | |
| 196 | + | version: Option<&Option<String>>, | |
| 197 | + | area: Rect, | |
| 198 | + | ) { | |
| 142 | 199 | let [_, right] = | |
| 143 | 200 | Layout::horizontal([Constraint::Length(2), Constraint::Min(0)]).areas(area); | |
| 144 | 201 | let [_, body] = | |
| @@ -149,6 +206,14 @@ fn draw_detail(frame: &mut Frame, role: &Role, area: Rect) { | |||
| 149 | 206 | ||
| 150 | 207 | lines.push(field_line("role", &role.name, label_w)); | |
| 151 | 208 | lines.push(field_line("tool", &role.tool, label_w)); | |
| 209 | + | ||
| 210 | + | let version_value: String = match version { | |
| 211 | + | Some(Some(v)) => v.clone(), | |
| 212 | + | Some(None) => "—".into(), | |
| 213 | + | None => "…".into(), | |
| 214 | + | }; | |
| 215 | + | lines.push(field_line("version", &version_value, label_w)); | |
| 216 | + | ||
| 152 | 217 | lines.push(field_line( | |
| 153 | 218 | "packages", | |
| 154 | 219 | &role.packages.join(", "), |
| @@ -8,6 +8,7 @@ mod storage; | |||
| 8 | 8 | mod wifi; | |
| 9 | 9 | ||
| 10 | 10 | use clap::{Parser, Subcommand}; | |
| 11 | + | use roles::Profile; | |
| 11 | 12 | ||
| 12 | 13 | #[derive(Parser)] | |
| 13 | 14 | #[command(name = "sysop", about = "Mountaineer operator interface")] | |
| @@ -22,6 +23,12 @@ enum Command { | |||
| 22 | 23 | Info { | |
| 23 | 24 | /// Role name (omit for the full list) | |
| 24 | 25 | role: Option<String>, | |
| 26 | + | /// Limit to roles in the Server build | |
| 27 | + | #[arg(long, conflicts_with = "sysadmin")] | |
| 28 | + | server: bool, | |
| 29 | + | /// Limit to roles in the Sysadmin build | |
| 30 | + | #[arg(long)] | |
| 31 | + | sysadmin: bool, | |
| 25 | 32 | }, | |
| 26 | 33 | /// Login tip rotation (on/off/print) | |
| 27 | 34 | Motd { | |
| @@ -72,7 +79,16 @@ enum MotdAction { | |||
| 72 | 79 | fn main() -> anyhow::Result<()> { | |
| 73 | 80 | let cli = Cli::parse(); | |
| 74 | 81 | let code = match cli.command { | |
| 75 | - | Command::Info { role } => info::run(role)?, | |
| 82 | + | Command::Info { role, server, sysadmin } => { | |
| 83 | + | let profile = if server { | |
| 84 | + | Some(Profile::Server) | |
| 85 | + | } else if sysadmin { | |
| 86 | + | Some(Profile::Sysadmin) | |
| 87 | + | } else { | |
| 88 | + | None | |
| 89 | + | }; | |
| 90 | + | info::run(role, profile)? | |
| 91 | + | } | |
| 76 | 92 | Command::Motd { action } => match action { | |
| 77 | 93 | None => motd::status()?, | |
| 78 | 94 | Some(MotdAction::On) => motd::set(true)?, |
| @@ -3,6 +3,18 @@ use serde::Deserialize; | |||
| 3 | 3 | ||
| 4 | 4 | const EMBEDDED: &str = include_str!("../data/roles.toml"); | |
| 5 | 5 | ||
| 6 | + | #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] | |
| 7 | + | #[serde(rename_all = "lowercase")] | |
| 8 | + | pub enum Profile { | |
| 9 | + | /// Server build only | |
| 10 | + | Server, | |
| 11 | + | /// Sysadmin build only (graphical layer) | |
| 12 | + | Sysadmin, | |
| 13 | + | /// Both builds | |
| 14 | + | #[serde(other)] | |
| 15 | + | Both, | |
| 16 | + | } | |
| 17 | + | ||
| 6 | 18 | #[derive(Debug, Clone, Deserialize)] | |
| 7 | 19 | pub struct Role { | |
| 8 | 20 | pub name: String, | |
| @@ -10,6 +22,27 @@ pub struct Role { | |||
| 10 | 22 | pub tool: String, | |
| 11 | 23 | pub packages: Vec<String>, | |
| 12 | 24 | pub config_paths: Vec<String>, | |
| 25 | + | /// Which build(s) this role appears in. Defaults to both. | |
| 26 | + | #[serde(default = "default_profile")] | |
| 27 | + | pub profile: Profile, | |
| 28 | + | /// Optional argv to invoke for `--version` display. | |
| 29 | + | /// First element is the program, the rest are arguments. | |
| 30 | + | #[serde(default)] | |
| 31 | + | pub version_cmd: Option<Vec<String>>, | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | fn default_profile() -> Profile { | |
| 35 | + | Profile::Both | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | impl Role { | |
| 39 | + | pub fn included_in(&self, filter: Option<Profile>) -> bool { | |
| 40 | + | match (filter, self.profile) { | |
| 41 | + | (None, _) => true, | |
| 42 | + | (Some(_), Profile::Both) => true, | |
| 43 | + | (Some(f), p) => f == p, | |
| 44 | + | } | |
| 45 | + | } | |
| 13 | 46 | } | |
| 14 | 47 | ||
| 15 | 48 | #[derive(Debug, Deserialize)] |