Fix git deploy .git/config permission denied via docker exec fallback
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+82
-15
@@ -17,25 +17,92 @@ def get_git_remote():
|
||||
return os.getenv("GIT_REMOTE_URL", "").strip()
|
||||
|
||||
|
||||
def get_container_name():
|
||||
return os.getenv("CONTAINER_NAME", os.getenv("HOSTNAME", "photohost-web"))
|
||||
|
||||
|
||||
def _repo_ready():
|
||||
repo = get_repo_path()
|
||||
return os.path.isdir(repo) and os.path.isdir(os.path.join(repo, ".git"))
|
||||
|
||||
|
||||
def _docker_available():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker", "info"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
return result.returncode == 0
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
return False
|
||||
|
||||
|
||||
def _git_base_cmd(repo):
|
||||
return ["git", "-c", f"safe.directory={repo}", "-C", repo]
|
||||
|
||||
|
||||
def _run_subprocess(cmd, timeout=120, cwd=None):
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
cwd=cwd,
|
||||
)
|
||||
output = (result.stderr or result.stdout or "").strip()
|
||||
return result.returncode == 0, output
|
||||
|
||||
|
||||
def _run_git_as_root(args, timeout=120):
|
||||
repo = get_repo_path()
|
||||
container = get_container_name()
|
||||
cmd = ["docker", "exec", "-u", "0", container] + _git_base_cmd(repo) + args
|
||||
return _run_subprocess(cmd, timeout=timeout)
|
||||
|
||||
|
||||
def _fix_repo_permissions():
|
||||
if not _docker_available() or not _repo_ready():
|
||||
return False
|
||||
|
||||
repo = get_repo_path()
|
||||
container = get_container_name()
|
||||
fix_cmd = [
|
||||
"docker",
|
||||
"exec",
|
||||
"-u",
|
||||
"0",
|
||||
container,
|
||||
"sh",
|
||||
"-c",
|
||||
f"chown -R appuser:appuser {repo} && chmod -R u+rwX {repo}/.git",
|
||||
]
|
||||
ok, _ = _run_subprocess(fix_cmd, timeout=60)
|
||||
return ok
|
||||
|
||||
|
||||
def run_git(args, timeout=120):
|
||||
if not _repo_ready():
|
||||
return False, f"Git-репозиторий не найден: {get_repo_path()}"
|
||||
|
||||
repo = get_repo_path()
|
||||
result = subprocess.run(
|
||||
["git", "-c", f"safe.directory={repo}", "-C", repo] + args,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return False, (result.stderr or result.stdout or "Git error").strip()
|
||||
return True, result.stdout.strip()
|
||||
cmd = _git_base_cmd(repo) + args
|
||||
ok, output = _run_subprocess(cmd, timeout=timeout)
|
||||
if ok:
|
||||
return True, output
|
||||
|
||||
permission_error = "permission denied" in output.lower() and "config" in output.lower()
|
||||
if permission_error and _docker_available():
|
||||
_fix_repo_permissions()
|
||||
ok, output = _run_subprocess(cmd, timeout=timeout)
|
||||
if ok:
|
||||
return True, output
|
||||
ok, output = _run_git_as_root(args, timeout=timeout)
|
||||
if ok:
|
||||
return True, output
|
||||
|
||||
return False, output or "Git error"
|
||||
|
||||
|
||||
def run_ls_remote(extra_args=None, timeout=60):
|
||||
@@ -47,10 +114,10 @@ def run_ls_remote(extra_args=None, timeout=60):
|
||||
if extra_args:
|
||||
cmd.extend(extra_args)
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||||
if result.returncode != 0:
|
||||
return False, (result.stderr or result.stdout or "ls-remote error").strip(), []
|
||||
return True, "", result.stdout.splitlines()
|
||||
ok, output = _run_subprocess(cmd, timeout=timeout)
|
||||
if not ok:
|
||||
return False, output or "ls-remote error", []
|
||||
return True, "", output.splitlines()
|
||||
|
||||
|
||||
def fetch_remote():
|
||||
@@ -58,14 +125,14 @@ def fetch_remote():
|
||||
if not remote:
|
||||
return run_git(["fetch", "--all", "--tags", "--prune"], timeout=180)
|
||||
|
||||
# Fetch from URL directly — do not write remote.origin.url to .git/config
|
||||
# Fetch by URL only — never run `git remote set-url` or other config writes.
|
||||
return run_git(
|
||||
[
|
||||
"fetch",
|
||||
"--tags",
|
||||
"--prune",
|
||||
remote,
|
||||
"+refs/heads/*:refs/remotes/origin/*",
|
||||
"+refs/heads/*:refs/heads/*",
|
||||
"+refs/tags/*:refs/tags/*",
|
||||
],
|
||||
timeout=180,
|
||||
|
||||
@@ -33,6 +33,7 @@ services:
|
||||
GIT_REPO_PATH: /repo
|
||||
GIT_REMOTE_URL: ${GIT_REMOTE_URL:-https://git.evilfox.cc/test2/fotohost.git}
|
||||
ALLOW_GIT_DEPLOY: ${ALLOW_GIT_DEPLOY:-false}
|
||||
CONTAINER_NAME: photohost-web
|
||||
GIT_CONFIG_COUNT: "1"
|
||||
GIT_CONFIG_KEY_0: safe.directory
|
||||
GIT_CONFIG_VALUE_0: /repo
|
||||
|
||||
+4
-1
@@ -3,7 +3,10 @@ set -e
|
||||
|
||||
# Mounted /repo belongs to host user; appuser needs write access for git deploy.
|
||||
if [ "$ALLOW_GIT_DEPLOY" = "true" ] || [ "$ALLOW_GIT_DEPLOY" = "1" ] || [ "$ALLOW_GIT_DEPLOY" = "yes" ]; then
|
||||
if [ -d /repo ]; then
|
||||
if [ -d /repo/.git ]; then
|
||||
chown -R appuser:appuser /repo
|
||||
chmod -R u+rwX /repo/.git
|
||||
elif [ -d /repo ]; then
|
||||
chown -R appuser:appuser /repo
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user