diff --git a/app/deploy_utils.py b/app/deploy_utils.py index b98300c..3089a21 100644 --- a/app/deploy_utils.py +++ b/app/deploy_utils.py @@ -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, diff --git a/docker-compose.yml b/docker-compose.yml index 8178c27..4700a32 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/entrypoint.sh b/entrypoint.sh index c8093c6..c537269 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -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