Skip to content

Commit 4362965

Browse files
committed
Make ssh setup configure Flow and key name
1 parent 761285b commit 4362965

File tree

1 file changed

+137
-1
lines changed

1 file changed

+137
-1
lines changed

src/ssh_keys.rs

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const KEY_PUBLIC: &str = "SSH_PUBLIC_KEY";
1616
const KEY_FINGERPRINT: &str = "SSH_FINGERPRINT";
1717

1818
pub(crate) const DEFAULT_KEY_NAME: &str = "default";
19+
const DEFAULT_SSH_MODE: &str = "force";
1920

2021
pub fn run(action: Option<SshAction>) -> Result<()> {
2122
match action {
@@ -31,7 +32,8 @@ pub(crate) fn ensure_default_identity(ttl_hours: u64) -> Result<()> {
3132
return Ok(());
3233
}
3334

34-
unlock(DEFAULT_KEY_NAME, ttl_hours)
35+
let key_name = configured_key_name();
36+
unlock(&key_name, ttl_hours)
3537
}
3638

3739
fn setup(name: &str, unlock_after: bool) -> Result<()> {
@@ -82,6 +84,7 @@ fn setup(name: &str, unlock_after: bool) -> Result<()> {
8284
println!("Stored SSH key in 1focus as '{}'.", key_name);
8385
println!("Public key:\n{}", public_key.trim());
8486
println!("Add it to GitHub: https://github.com/settings/keys");
87+
ensure_global_ssh_config(&key_name)?;
8588

8689
if unlock_after {
8790
unlock(&key_name, DEFAULT_TTL_HOURS)?;
@@ -170,6 +173,121 @@ fn status(name: &str) -> Result<()> {
170173
Ok(())
171174
}
172175

176+
fn ensure_global_ssh_config(key_name: &str) -> Result<()> {
177+
let config_path = config::default_config_path();
178+
if let Some(parent) = config_path.parent() {
179+
fs::create_dir_all(parent)
180+
.with_context(|| format!("failed to create {}", parent.display()))?;
181+
}
182+
183+
let contents = if config_path.exists() {
184+
fs::read_to_string(&config_path)
185+
.with_context(|| format!("failed to read {}", config_path.display()))?
186+
} else {
187+
String::new()
188+
};
189+
190+
let updated = upsert_ssh_block(&contents, DEFAULT_SSH_MODE, key_name);
191+
if updated != contents {
192+
fs::write(&config_path, updated)
193+
.with_context(|| format!("failed to write {}", config_path.display()))?;
194+
println!(
195+
"Configured Flow to use SSH keys from 1focus (mode={}, key={}).",
196+
DEFAULT_SSH_MODE, key_name
197+
);
198+
}
199+
200+
Ok(())
201+
}
202+
203+
fn upsert_ssh_block(input: &str, mode: &str, key_name: &str) -> String {
204+
let mut out: Vec<String> = Vec::new();
205+
let mut in_ssh = false;
206+
let mut saw_ssh = false;
207+
let mut saw_mode = false;
208+
let mut saw_key = false;
209+
let ends_with_newline = input.ends_with('\n');
210+
211+
for line in input.lines() {
212+
let trimmed = line.trim();
213+
if trimmed.starts_with('[') && trimmed.ends_with(']') {
214+
if in_ssh {
215+
if !saw_mode {
216+
out.push(format!("mode = \"{}\"", mode));
217+
saw_mode = true;
218+
}
219+
if !saw_key {
220+
out.push(format!("key_name = \"{}\"", key_name));
221+
saw_key = true;
222+
}
223+
}
224+
225+
in_ssh = trimmed == "[ssh]";
226+
if in_ssh {
227+
saw_ssh = true;
228+
}
229+
out.push(line.to_string());
230+
continue;
231+
}
232+
233+
if in_ssh {
234+
if trimmed.starts_with("mode") && trimmed.contains('=') {
235+
out.push(format!("mode = \"{}\"", mode));
236+
saw_mode = true;
237+
continue;
238+
}
239+
if trimmed.starts_with("key_name") && trimmed.contains('=') {
240+
out.push(format!("key_name = \"{}\"", key_name));
241+
saw_key = true;
242+
continue;
243+
}
244+
}
245+
246+
out.push(line.to_string());
247+
}
248+
249+
if in_ssh {
250+
if !saw_mode {
251+
out.push(format!("mode = \"{}\"", mode));
252+
}
253+
if !saw_key {
254+
out.push(format!("key_name = \"{}\"", key_name));
255+
}
256+
}
257+
258+
if !saw_ssh {
259+
if !out.is_empty() {
260+
out.push(String::new());
261+
}
262+
out.push("[ssh]".to_string());
263+
out.push(format!("mode = \"{}\"", mode));
264+
out.push(format!("key_name = \"{}\"", key_name));
265+
}
266+
267+
let mut rendered = out.join("\n");
268+
if ends_with_newline || rendered.is_empty() {
269+
rendered.push('\n');
270+
}
271+
rendered
272+
}
273+
274+
fn configured_key_name() -> String {
275+
let config_path = config::default_config_path();
276+
if config_path.exists() {
277+
if let Ok(cfg) = config::load(&config_path) {
278+
if let Some(ssh_cfg) = cfg.ssh {
279+
if let Some(name) = ssh_cfg.key_name {
280+
if !name.trim().is_empty() {
281+
return name;
282+
}
283+
}
284+
}
285+
}
286+
}
287+
288+
DEFAULT_KEY_NAME.to_string()
289+
}
290+
173291
fn key_env_keys(name: &str) -> (String, String, String) {
174292
if name == "default" {
175293
(
@@ -284,4 +402,22 @@ mod tests {
284402
assert_eq!(mode, 0o600);
285403
}
286404
}
405+
406+
#[test]
407+
fn upsert_ssh_block_adds_when_missing() {
408+
let updated = upsert_ssh_block("", "force", "default");
409+
assert!(updated.contains("[ssh]"));
410+
assert!(updated.contains("mode = \"force\""));
411+
assert!(updated.contains("key_name = \"default\""));
412+
}
413+
414+
#[test]
415+
fn upsert_ssh_block_updates_existing_values() {
416+
let input = "[ssh]\nmode = \"auto\"\nkey_name = \"work\"\n";
417+
let updated = upsert_ssh_block(input, "force", "default");
418+
assert!(updated.contains("mode = \"force\""));
419+
assert!(updated.contains("key_name = \"default\""));
420+
assert!(!updated.contains("mode = \"auto\""));
421+
assert!(!updated.contains("key_name = \"work\""));
422+
}
287423
}

0 commit comments

Comments
 (0)