From 9b2600659793160418b9e5a5d31672f71e4e964a Mon Sep 17 00:00:00 2001 From: poprhythm Date: Sun, 13 Sep 2026 15:38:34 +0000 Subject: [PATCH] Add portainer.sh fix-git-auth for stacks with lost GitConfig credentials The ollama stack's GitConfig.Authentication.Password had gone empty (same class of issue as the prior Gitea log-poisoning/token-rotation work), causing redeploy to fail with "Unable to clone git repository directory". This resupplies repositoryUsername/repositoryPassword from GITEA_USER/GITEA_TOKEN on redeploy, scoped to a new subcommand rather than changing default redeploy behavior for every stack. Claude-Session: https://claude.ai/code/session_01L7Rwa6guD5wK8F8tWQwcJX --- portainer.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/portainer.sh b/portainer.sh index ada9459..287b90e 100755 --- a/portainer.sh +++ b/portainer.sh @@ -130,6 +130,61 @@ print(f'Done. ConfigHash: {hash}') " "$response" } +cmd_fix_git_auth() { + local name="${1:-}" + if [[ -z "$name" ]]; then + echo "Usage: $0 fix-git-auth " >&2 + exit 1 + fi + if [[ -z "${GITEA_USER:-}" || -z "${GITEA_TOKEN:-}" ]]; then + echo "Error: GITEA_USER/GITEA_TOKEN not set in .credentials" >&2 + exit 1 + fi + + echo "Looking up stack '$name'..." + local stack_json + if ! stack_json=$(get_stack_json_by_name "$name"); then + echo "Error: stack '$name' not found" >&2 + exit 1 + fi + local stack_id + stack_id=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['Id'])" "$stack_json") + echo "Found stack ID: $stack_id" + + # Re-supplies repository credentials that Portainer's stored GitConfig can + # lose (empty Authentication.Password) — the symptom is git/redeploy + # failing with "Unable to clone git repository directory". + local payload + payload=$(GITEA_USER="$GITEA_USER" GITEA_TOKEN="$GITEA_TOKEN" python3 -c " +import json, os, sys +stack = json.loads(sys.argv[1]) +env = stack.get('Env') or [] +print(json.dumps({ + 'pullImage': True, + 'prune': False, + 'env': env, + 'repositoryAuthentication': True, + 'repositoryUsername': os.environ['GITEA_USER'], + 'repositoryPassword': os.environ['GITEA_TOKEN'], +})) +" "$stack_json") + + echo "Re-authenticating and redeploying..." + local response + response=$(api_put "stacks/${stack_id}/git/redeploy?endpointId=${ENDPOINT_ID}" "$payload") + + python3 -c " +import json, sys +d = json.loads(sys.argv[1]) +if 'message' in d and 'Id' not in d: + print('Error:', d['message']) + sys.exit(1) +hash = d.get('GitConfig', {}).get('ConfigHash', 'unknown')[:10] +pw_set = bool(d.get('GitConfig', {}).get('Authentication', {}).get('Password')) +print(f'Done. ConfigHash: {hash}. Credential stored: {pw_set}') +" "$response" +} + cmd_deploy() { local name="${1:-}" local compose_path="${2:-}" @@ -292,6 +347,7 @@ command="${1:-}" case "$command" in list) cmd_list ;; redeploy) cmd_redeploy "${2:-}" ;; + fix-git-auth) cmd_fix_git_auth "${2:-}" ;; deploy) cmd_deploy "${@:2}" ;; get-env) cmd_get_env "${2:-}" ;; set-env) cmd_set_env "${2:-}" "${@:3}" ;; @@ -304,6 +360,7 @@ case "$command" in echo " deploy [K=V ...] Create new git-linked stack with optional env vars" echo " get-env Show env vars for a stack" echo " set-env KEY=VAL [...] Set env vars (redeploys without new image pull)" + echo " fix-git-auth Re-supply git credentials when redeploy fails with 'Unable to clone'" exit 1 ;; esac