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()
|
return os.getenv("GIT_REMOTE_URL", "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def get_container_name():
|
||||||
|
return os.getenv("CONTAINER_NAME", os.getenv("HOSTNAME", "photohost-web"))
|
||||||
|
|
||||||
|
|
||||||
def _repo_ready():
|
def _repo_ready():
|
||||||
repo = get_repo_path()
|
repo = get_repo_path()
|
||||||
return os.path.isdir(repo) and os.path.isdir(os.path.join(repo, ".git"))
|
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):
|
def run_git(args, timeout=120):
|
||||||
if not _repo_ready():
|
if not _repo_ready():
|
||||||
return False, f"Git-репозиторий не найден: {get_repo_path()}"
|
return False, f"Git-репозиторий не найден: {get_repo_path()}"
|
||||||
|
|
||||||
repo = get_repo_path()
|
repo = get_repo_path()
|
||||||
result = subprocess.run(
|
cmd = _git_base_cmd(repo) + args
|
||||||
["git", "-c", f"safe.directory={repo}", "-C", repo] + args,
|
ok, output = _run_subprocess(cmd, timeout=timeout)
|
||||||
capture_output=True,
|
if ok:
|
||||||
text=True,
|
return True, output
|
||||||
timeout=timeout,
|
|
||||||
)
|
permission_error = "permission denied" in output.lower() and "config" in output.lower()
|
||||||
if result.returncode != 0:
|
if permission_error and _docker_available():
|
||||||
return False, (result.stderr or result.stdout or "Git error").strip()
|
_fix_repo_permissions()
|
||||||
return True, result.stdout.strip()
|
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):
|
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:
|
if extra_args:
|
||||||
cmd.extend(extra_args)
|
cmd.extend(extra_args)
|
||||||
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
ok, output = _run_subprocess(cmd, timeout=timeout)
|
||||||
if result.returncode != 0:
|
if not ok:
|
||||||
return False, (result.stderr or result.stdout or "ls-remote error").strip(), []
|
return False, output or "ls-remote error", []
|
||||||
return True, "", result.stdout.splitlines()
|
return True, "", output.splitlines()
|
||||||
|
|
||||||
|
|
||||||
def fetch_remote():
|
def fetch_remote():
|
||||||
@@ -58,14 +125,14 @@ def fetch_remote():
|
|||||||
if not remote:
|
if not remote:
|
||||||
return run_git(["fetch", "--all", "--tags", "--prune"], timeout=180)
|
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(
|
return run_git(
|
||||||
[
|
[
|
||||||
"fetch",
|
"fetch",
|
||||||
"--tags",
|
"--tags",
|
||||||
"--prune",
|
"--prune",
|
||||||
remote,
|
remote,
|
||||||
"+refs/heads/*:refs/remotes/origin/*",
|
"+refs/heads/*:refs/heads/*",
|
||||||
"+refs/tags/*:refs/tags/*",
|
"+refs/tags/*:refs/tags/*",
|
||||||
],
|
],
|
||||||
timeout=180,
|
timeout=180,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ services:
|
|||||||
GIT_REPO_PATH: /repo
|
GIT_REPO_PATH: /repo
|
||||||
GIT_REMOTE_URL: ${GIT_REMOTE_URL:-https://git.evilfox.cc/test2/fotohost.git}
|
GIT_REMOTE_URL: ${GIT_REMOTE_URL:-https://git.evilfox.cc/test2/fotohost.git}
|
||||||
ALLOW_GIT_DEPLOY: ${ALLOW_GIT_DEPLOY:-false}
|
ALLOW_GIT_DEPLOY: ${ALLOW_GIT_DEPLOY:-false}
|
||||||
|
CONTAINER_NAME: photohost-web
|
||||||
GIT_CONFIG_COUNT: "1"
|
GIT_CONFIG_COUNT: "1"
|
||||||
GIT_CONFIG_KEY_0: safe.directory
|
GIT_CONFIG_KEY_0: safe.directory
|
||||||
GIT_CONFIG_VALUE_0: /repo
|
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.
|
# 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 [ "$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
|
chown -R appuser:appuser /repo
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user