Skip to main content

max / makenotwork

server: derive git authorized_keys path from GIT_HOME Un-hardcode /opt/git/.ssh/authorized_keys, which forced a load-bearing /opt/git -> /var/lib/mnw/git symlink after the soak cleanup deleted /opt/git. authorized_keys_path() now derives from a GIT_HOME env var (default /opt/git, matching deploy/setup-git-ssh.sh), and mnw-admin's setup-git routes through the same helper so the path lives in one place. Default behavior unchanged; set GIT_HOME to retire the symlink. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-23 19:54 UTC
Commit: f1050c538aa000c6b767a2f5c096da761881718e
Parent: 84c3d29
2 files changed, +29 insertions, -9 deletions
@@ -123,7 +123,8 @@ enum Command {
123 123 /// Username to audit
124 124 username: String,
125 125 },
126 - /// Rebuild /opt/git/.ssh/authorized_keys from the database
126 + /// Rebuild the git user's authorized_keys (`$GIT_HOME/.ssh/authorized_keys`)
127 + /// from the database
127 128 RebuildKeys,
128 129 /// Authenticate an SSH git operation (called by sshd command= prefix)
129 130 GitAuth {
@@ -778,8 +779,10 @@ fn cmd_setup_git() -> anyhow::Result<()> {
778 779 use std::os::unix::fs::PermissionsExt;
779 780 use std::path::Path;
780 781
781 - let ssh_dir = Path::new("/opt/git/.ssh");
782 - let authorized_keys = ssh_dir.join("authorized_keys");
782 + let authorized_keys = makenotwork::git_ssh::authorized_keys_path();
783 + let ssh_dir = authorized_keys
784 + .parent()
785 + .expect("authorized_keys_path always has a .ssh parent");
783 786 let sudoers_file = Path::new("/etc/sudoers.d/mnw-git-ssh");
784 787 let mnw_admin = Path::new(makenotwork::git_ssh::MNW_ADMIN_PATH);
785 788
@@ -11,9 +11,24 @@ use crate::validation::validate_git_repo_name;
11 11
12 12 // ── Constants ──
13 13
14 - pub const AUTHORIZED_KEYS_PATH: &str = "/opt/git/.ssh/authorized_keys";
15 14 pub const MNW_ADMIN_PATH: &str = "/opt/mnw/current/mnw-admin";
16 15
16 + /// The git user's home directory (`GIT_HOME`, default `/opt/git`). Configurable
17 + /// because the home was relocated to `/var/lib/mnw/git` in the 2026-06 soak
18 + /// cleanup: `/opt/git` was deleted, and hardcoding it forced a load-bearing
19 + /// symlink so `rebuild-keys` would keep writing to a live path. Matches the
20 + /// `GIT_HOME` used by `deploy/setup-git-ssh.sh`.
21 + fn git_home() -> std::path::PathBuf {
22 + std::path::PathBuf::from(std::env::var("GIT_HOME").unwrap_or_else(|_| "/opt/git".to_string()))
23 + }
24 +
25 + /// Path to the git user's `authorized_keys`, managed by `mnw-admin rebuild-keys`
26 + /// and consulted by sshd's `command=` routing. Derived from [`git_home`] so a
27 + /// relocated home needs only `GIT_HOME` set, not a symlink.
28 + pub fn authorized_keys_path() -> std::path::PathBuf {
29 + git_home().join(".ssh").join("authorized_keys")
30 + }
31 +
17 32 // ── Git operations ──
18 33
19 34 #[derive(Debug)]
@@ -585,21 +600,23 @@ pub async fn write_authorized_keys(pool: &PgPool, set_ownership: bool) -> anyhow
585 600 ));
586 601 }
587 602
588 - let tmp_path = format!("{}.tmp", AUTHORIZED_KEYS_PATH);
603 + let keys_path = authorized_keys_path();
604 + let tmp_path = keys_path.with_extension("tmp");
589 605 std::fs::write(&tmp_path, &content)?;
590 - std::fs::rename(&tmp_path, AUTHORIZED_KEYS_PATH)?;
606 + std::fs::rename(&tmp_path, &keys_path)?;
591 607
592 608 #[cfg(unix)]
593 609 {
594 610 use std::os::unix::fs::PermissionsExt;
595 - std::fs::set_permissions(AUTHORIZED_KEYS_PATH, std::fs::Permissions::from_mode(0o600))?;
611 + std::fs::set_permissions(&keys_path, std::fs::Permissions::from_mode(0o600))?;
596 612
597 613 if set_ownership {
598 614 let status = std::process::Command::new("chown")
599 - .args(["git:git", AUTHORIZED_KEYS_PATH])
615 + .arg("git:git")
616 + .arg(&keys_path)
600 617 .status()?;
601 618 if !status.success() {
602 - anyhow::bail!("chown git:git failed on {}", AUTHORIZED_KEYS_PATH);
619 + anyhow::bail!("chown git:git failed on {}", keys_path.display());
603 620 }
604 621 }
605 622 }