Bump the workspace to 3.0.1 and bring the user guide up to date: an RDP chapter, shell integration and inline images, SSH certificates and CAs, known_hosts import/export, stored credentials and master password change, profile sync (replacing the Alchementary Link migration chapter), the Diagnostics tab and log, and refreshed screenshots throughout. The welcome screen's protocol line now lists RDP.
186 lines
7.9 KiB
Rust
186 lines
7.9 KiB
Rust
//! Fills a data directory with demo profiles, snippets, known hosts, trigger rules and recordings
|
|
//! for the user-guide screenshots (docs/guide/screenshots.sh).
|
|
//!
|
|
//! cargo run -p nexus-core --example demo_data -- DATA_DIR SSH_KEY
|
|
|
|
use nexus_core::config::AppConfig;
|
|
use nexus_core::models::*;
|
|
use nexus_core::service::Service;
|
|
|
|
fn main() {
|
|
let mut args = std::env::args().skip(1);
|
|
let (Some(dir), Some(key)) = (args.next(), args.next()) else {
|
|
eprintln!("usage: demo_data DATA_DIR SSH_KEY");
|
|
std::process::exit(2);
|
|
};
|
|
let config = AppConfig::in_dir(dir.into());
|
|
std::fs::create_dir_all(&config.vault_dir).unwrap();
|
|
std::fs::create_dir_all(&config.recordings_dir).unwrap();
|
|
let svc = Service::open(config).unwrap();
|
|
let user = std::env::var("USER").unwrap_or_else(|_| "andy".into());
|
|
|
|
let mk = |proto, name: &str, host: &str, port: i64, folder: &str, fav: bool| {
|
|
let mut p = Profile::new(proto);
|
|
p.display_name = name.into();
|
|
p.host = host.into();
|
|
if port != 0 {
|
|
p.port = port;
|
|
}
|
|
p.folder = folder.into();
|
|
p.favourite = fav;
|
|
p
|
|
};
|
|
let ssh = |name: &str, folder: &str, fav: bool, tags: &[&str]| {
|
|
let mut p = mk(Protocol::Ssh, name, "127.0.0.1", 42022, folder, fav);
|
|
p.username = user.clone();
|
|
p.tags = tags.iter().map(|t| t.to_string()).collect();
|
|
let s = p.ssh.as_mut().unwrap();
|
|
s.private_key_path = key.clone();
|
|
s.auth_methods = vec!["publickey".into()];
|
|
s.host_key_policy = HostKeyPolicy::AcceptNew;
|
|
p
|
|
};
|
|
|
|
let mut profiles = vec![];
|
|
let mut atlas = ssh("atlas", "Production", true, &["web", "eu-west"]);
|
|
atlas.description = "Primary web node".into();
|
|
atlas.notes = "Deploys go out from here. Be nice to it.".into();
|
|
profiles.push(atlas);
|
|
let mut db = ssh("db-primary", "Production", false, &["db"]);
|
|
db.description = "Postgres primary, via the bastion".into();
|
|
db.ssh.as_mut().unwrap().jump_hosts = vec![JumpHost {
|
|
host: "bastion.example.net".into(),
|
|
port: 22,
|
|
username: user.clone(),
|
|
auth_method: "publickey".into(),
|
|
secret_mode: SecretMode::Saved,
|
|
private_key_path: key.clone(),
|
|
}];
|
|
db.ssh.as_mut().unwrap().port_forwards = vec![PortForward {
|
|
id: new_id(),
|
|
direction: ForwardDirection::Local,
|
|
bind_host: "127.0.0.1".into(),
|
|
bind_port: 15432,
|
|
dest_host: "127.0.0.1".into(),
|
|
dest_port: 5432,
|
|
enabled: true,
|
|
label: "Postgres".into(),
|
|
}];
|
|
profiles.push(db);
|
|
let mut hello = ssh("hello-ssh", "Production", false, &["demo"]);
|
|
hello.ssh.as_mut().unwrap().host_key_policy = HostKeyPolicy::Ask;
|
|
hello.description = "Same box, but asks about its host key".into();
|
|
profiles.push(hello);
|
|
profiles.push(ssh("build-runner", "Lab", false, &["ci"]));
|
|
|
|
let mut router = mk(Protocol::Telnet, "Core router", "192.0.2.1", 0, "Lab", false);
|
|
router.description = "Out-of-band console".into();
|
|
profiles.push(router);
|
|
let mut console = mk(Protocol::Serial, "Switch console", "/dev/ttyUSB0", 0, "Lab", false);
|
|
console.serial.as_mut().unwrap().baud_rate = 9600;
|
|
profiles.push(console);
|
|
let mut sensor = mk(Protocol::Raw, "Sensor feed", "127.0.0.1", 42023, "Lab", false);
|
|
let raw = sensor.raw.as_mut().unwrap();
|
|
raw.show_timestamps = true;
|
|
raw.show_direction = true;
|
|
raw.line_ending = LineEnding::Lf;
|
|
raw.payload_presets = vec![
|
|
PayloadPreset { id: new_id(), name: "Status".into(), payload: "U1RBVFVT".into(), format: "text".into() },
|
|
PayloadPreset { id: new_id(), name: "Reset".into(), payload: "UkVTRVQ=".into(), format: "text".into() },
|
|
];
|
|
profiles.push(sensor);
|
|
|
|
let mut irc = mk(Protocol::Irc, "Libera Chat", "127.0.0.1", 42667, "Home", true);
|
|
let ic = irc.irc.as_mut().unwrap();
|
|
ic.nick = "andy".into();
|
|
ic.real_name = "Andy Dixon".into();
|
|
ic.use_tls = false;
|
|
ic.auto_join_rooms = vec!["#nexus".into(), "#rust".into()];
|
|
profiles.push(irc);
|
|
let mut gh = mk(Protocol::Github, "GitHub", "github.com", 0, "Home", false);
|
|
gh.github.as_mut().unwrap().base_url = "http://127.0.0.1:42080".into();
|
|
profiles.push(gh);
|
|
let mut vnc = mk(Protocol::Vnc, "Studio desktop", "127.0.0.1", 42900, "Home", false);
|
|
vnc.vnc.as_mut().unwrap().vnc_host = "127.0.0.1".into();
|
|
vnc.vnc.as_mut().unwrap().vnc_port = 42900;
|
|
vnc.vnc.as_mut().unwrap().scale_to_fit = true;
|
|
profiles.push(vnc);
|
|
let mut rdp = mk(Protocol::Rdp, "Design workstation", "192.0.2.60", 0, "Home", false);
|
|
rdp.username = "andy".into();
|
|
rdp.rdp.as_mut().unwrap().domain = "STUDIO".into();
|
|
profiles.push(rdp);
|
|
|
|
for p in profiles {
|
|
svc.create_profile(p).unwrap();
|
|
}
|
|
|
|
for (name, cmd, desc, folder) in [
|
|
("Disk usage", "df -h | sort -k5 -r | head", "Fullest filesystems first", "Diagnostics"),
|
|
("Who's hogging CPU", "ps aux --sort=-%cpu | head -n 8", "Top processes by CPU", "Diagnostics"),
|
|
("Tail a service", "journalctl -fu {{service}}", "Asks which service", "Logs"),
|
|
("Stamp the log", "echo '--- {{hostname}} {{date}} {{time}} ---'", "Drop a marker in the scrollback", "Logs"),
|
|
("Restart nginx", "sudo systemctl restart nginx && systemctl status nginx --no-pager", "", "Deploy"),
|
|
] {
|
|
svc.save_snippet(Snippet {
|
|
id: String::new(),
|
|
name: name.into(),
|
|
command: cmd.into(),
|
|
description: desc.into(),
|
|
folder: folder.into(),
|
|
tags: vec![],
|
|
created_at: now(),
|
|
updated_at: now(),
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
for (host, port, kind, fp) in [
|
|
("bastion.example.net", 22, "ssh-ed25519", "SHA256:x1rT0pV6c1m9QbHq4nWJcF5gk1Yt3uZ3w8sLrM2dE0Q"),
|
|
("192.0.2.40", 22, "ecdsa-sha2-nistp256", "SHA256:9Hq0b2VtZr6y1oQwA7mXJ4sEo5kP8fN3cLdT6uB2iRg"),
|
|
("git.example.org", 2222, "ssh-rsa", "SHA256:Qm7cV2pK9zR1tY5uW3xN8bL0aE6sD4fG2hJ1kM7nP5o"),
|
|
] {
|
|
let n = now();
|
|
svc.store
|
|
.save_known_host(&KnownHost {
|
|
id: String::new(),
|
|
host: host.into(),
|
|
port,
|
|
key_type: kind.into(),
|
|
fingerprint: fp.into(),
|
|
public_key: String::new(),
|
|
first_seen: n,
|
|
last_seen: n,
|
|
trusted: true,
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
std::fs::write(
|
|
svc.config.data_dir.join("triggers.json"),
|
|
r#"[{"name":"Errors","pattern":"(?i)\\b(error|fatal|panic)\\b","isRegex":true,"action":"both","caseSensitive":false,"enabled":true},
|
|
{"name":"Build finished","pattern":"BUILD SUCCESSFUL","isRegex":false,"action":"sound","caseSensitive":true,"enabled":true},
|
|
{"name":"Disk nearly full","pattern":"9[0-9]%","isRegex":true,"action":"highlight","caseSensitive":false,"enabled":false}]"#,
|
|
)
|
|
.unwrap();
|
|
|
|
let start = 1_791_633_486u64;
|
|
for (i, (title, lines)) in [
|
|
("atlas — deploy", &["$ ./deploy.sh\r\n", "\x1b[32m✔\x1b[0m built in 41s\r\n", "\x1b[32m✔\x1b[0m rolled out to 4/4 nodes\r\n"][..]),
|
|
("Switch console", &["Switch> enable\r\n", "Switch# show version\r\n", "Uptime is 212 days\r\n"][..]),
|
|
("Local Machine", &["$ cargo test\r\n", "test result: \x1b[32mok\x1b[0m. 135 passed\r\n"][..]),
|
|
]
|
|
.iter()
|
|
.enumerate()
|
|
{
|
|
let mut body = format!(
|
|
"{{\"version\":2,\"width\":100,\"height\":30,\"timestamp\":{},\"title\":\"{title}\",\"env\":{{\"TERM\":\"xterm-256color\"}}}}\n",
|
|
start - i as u64 * 86_400 * 3
|
|
);
|
|
for (j, l) in lines.iter().enumerate() {
|
|
body += &format!("[{}.5,\"o\",{}]\n", j * 2, serde_json::to_string(l).unwrap());
|
|
}
|
|
std::fs::write(svc.config.recordings_dir.join(format!("demo-{i}.cast")), body).unwrap();
|
|
}
|
|
println!("demo data written to {}", svc.config.data_dir.display());
|
|
}
|