Skip to main content

max / makenotwork

Fix git auto-create: use group permissions instead of sudo sudo fails under NoNewPrivileges=true (systemd hardening). Instead, add the makenotwork user to the git group and make owner dirs group-writable. Reverts to direct git2::Repository::init_bare(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-03 00:10 UTC
Commit: 9f103ba81cb696af4f6f91c1e9f1fbdb7f58b65d
Parent: ef31775
2 files changed, +17 insertions, -40 deletions
@@ -69,29 +69,18 @@ async fn exec_git_operation(
69 69
70 70 let git_root = std::env::var("GIT_REPOS_PATH")
71 71 .unwrap_or_else(|_| "/opt/git".to_string());
72 - let repo_dir = std::path::Path::new(&git_root)
73 - .join(owner)
74 - .join(format!("{repo_name}.git"));
75 -
76 - // Create the bare repo as the git user (the server process
77 - // doesn't have write permissions to the git:git owned directory).
78 - let status = std::process::Command::new("sudo")
79 - .args(["-u", "git", "git", "init", "--bare"])
80 - .arg(&repo_dir)
81 - .status()?;
82 - if !status.success() {
83 - anyhow::bail!("git init --bare failed for {}", repo_dir.display());
84 - }
72 + let owner_dir = std::path::Path::new(&git_root).join(owner);
73 + let repo_dir = owner_dir.join(format!("{repo_name}.git"));
74 +
75 + // The server user is in the git group and owner dirs are
76 + // group-writable, so we can create repos directly via git2.
77 + std::fs::create_dir_all(&owner_dir)?;
78 + git2::Repository::init_bare(&repo_dir)?;
85 79
86 80 // Install post-receive hook if build trigger token is configured
87 81 if let Ok(token) = std::env::var("BUILD_TRIGGER_TOKEN") {
88 82 let hook_content = crate::build_runner::post_receive_hook(&token);
89 83 install_hook_for_repo(&repo_dir, &hook_content)?;
90 - // Fix hook ownership
91 - let _ = std::process::Command::new("chown")
92 - .args(["git:git"])
93 - .arg(repo_dir.join("hooks/post-receive"))
94 - .status();
95 84 }
96 85
97 86 eprintln!("Auto-created repository {}/{}", owner, repo_name);
@@ -147,30 +147,22 @@ pub(super) async fn git_authorize(
147 147 return Err(AppError::NotFound);
148 148 }
149 149
150 - let repo_dir = std::path::Path::new(git_root)
151 - .join(&req.owner)
152 - .join(format!("{}.git", req.repo_name));
150 + let owner_dir = std::path::Path::new(git_root).join(&req.owner);
151 + let repo_dir = owner_dir.join(format!("{}.git", req.repo_name));
153 152 fn io_err(e: std::io::Error) -> AppError { AppError::Internal(e.into()) }
154 153
155 - // Create the bare repo as the git user (the makenotwork service user
156 - // doesn't have write permissions to the git:git owned directory).
157 - let status = std::process::Command::new("sudo")
158 - .args(["-u", "git", "git", "init", "--bare"])
159 - .arg(&repo_dir)
160 - .status()
161 - .map_err(io_err)?;
162 - if !status.success() {
163 - return Err(AppError::Internal(anyhow::anyhow!(
164 - "git init --bare failed for {}",
165 - repo_dir.display()
166 - )));
167 - }
154 + // The makenotwork user is in the git group and owner dirs are
155 + // group-writable, so we can create repos directly via git2.
156 + std::fs::create_dir_all(&owner_dir).map_err(io_err)?;
157 + git2::Repository::init_bare(&repo_dir)
158 + .map_err(|e| AppError::Internal(e.into()))?;
168 159
169 160 // Install post-receive hook if build trigger token is configured
170 161 if let Some(ref token) = state.config.build_trigger_token {
171 162 let hook_content = crate::build_runner::post_receive_hook(token);
172 - let hook_path = repo_dir.join("hooks/post-receive");
173 - // Write as root, then chown — hooks dir already exists from git init
163 + let hooks_dir = repo_dir.join("hooks");
164 + std::fs::create_dir_all(&hooks_dir).map_err(io_err)?;
165 + let hook_path = hooks_dir.join("post-receive");
174 166 std::fs::write(&hook_path, &hook_content).map_err(io_err)?;
175 167 #[cfg(unix)]
176 168 {
@@ -180,10 +172,6 @@ pub(super) async fn git_authorize(
180 172 std::fs::Permissions::from_mode(0o755),
181 173 );
182 174 }
183 - let _ = std::process::Command::new("chown")
184 - .args(["git:git"])
185 - .arg(&hook_path)
186 - .status();
187 175 }
188 176
189 177 tracing::info!(owner = %req.owner, repo = %req.repo_name, "auto-created bare repository");