fix(keycloak,friendica): keep shipped scripts off the caller's terminal

Three jobs of run 39186571513 hung and were killed by the outer
timeout 9000: Friendica#1 at 9003.17s, OpenTalk#0 at 9004.66s,
Agent-Broker#0 at 9006.62s, each after idling 1h37m to 2h22m. Two
stopped at the same inner task, web-app-keycloak's realm design sync;
the third at Friendica's addon sync.

Not slowness. In the same OpenTalk job the same keycloak task also ran
in the outer deploy over the local connection and finished in 4.90s.

The chain is three parts, each of which is ordinary on its own:

  ansible.builtin.script passes no in_data, so ansible adds -tt and the
  remote shell gets a pty (connection/ssh.py checks 'not in_data');
  container exec wraps docker in coreutils timeout, which without
  --foreground calls setpgid; docker exec -i then reads that controlling
  tty from a background process group, takes SIGTTIN and stops forever.

A sibling Friendica task running the same container exec -i through the
shell module passed in 0.6s, because shell carries its module payload as
in_data and therefore never gets -tt.

Nothing rescued it: SYS_TIMEOUT_CONTAINER_EXEC is 7d, so the OS-level
kill was a week away and the CI budget arrived first. In the field,
following the published instructions, it simply hangs.

Closing stdin in the script removes the read. The -i stays on the exec
prefixes: of the six execution sites behind KEYCLOAK_EXEC_CONTAINER,
ensure_realm_user.sh pipes a credential document into reset-password
-f -, and four more sites behind the derived KEYCLOAK_EXEC_KCADM pipe
as well, so dropping -i would reset passwords from an empty document
without erroring.

Safe for every script here: none reads its own stdin. The three
while-read loops are fed from files, two reads use here-strings, both
piped execs override fd 0 themselves, and the nested sh -lc bodies
travel as argv. Three exec sites already carried an inline </dev/null,
which is the same trap found and patched locally before.

Ten of the twelve never hung in this run only because LDAP was
disabled; they reach the same exec the same way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-10-20 18:55:13 +02:00
1 parent 8bb500a833
commit d31b49e30d
12 files changed
+24

No files matched your search

@@ -28,6 +28,8 @@ set -euo pipefail
EXEC="$1"
P="$2"
exec </dev/null
ORIG=" return \$user;"
NEW=" return \$user['user'] ?? \$user;"
@@ -23,6 +23,8 @@ CMD="$1"
ADDON="$2"
DESIRED="$3"
exec </dev/null
if [[ "$DESIRED" == "true" ]]; then
$CMD addon disable "$ADDON" 2>&1 || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
$CMD addon enable "$ADDON" 2>&1 || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
@@ -14,6 +14,8 @@ set -o pipefail
: "${KC_REALM:?KC_REALM is required}"
: "${LDAP_COMPONENT_ID:?LDAP_COMPONENT_ID is required}"
exec </dev/null
container exec -i "$KC_CONTAINER" /opt/keycloak/bin/kcadm.sh get components \
-r "$KC_REALM" \
-q "parentId=$LDAP_COMPONENT_ID" \
@@ -13,6 +13,8 @@ set -o pipefail
: "${APP_ID:?APP_ID is required}"
: "${ROOT_GROUP_ID:?ROOT_GROUP_ID is required}"
exec </dev/null
container exec -i "$KC_CONTAINER" /opt/keycloak/bin/kcadm.sh get \
"groups/$ROOT_GROUP_ID/children" -r "$KC_REALM" \
-q max=500 --fields id,name --format csv --noquotes \
@@ -12,6 +12,8 @@ set -o pipefail
: "${KC_REALM:?KC_REALM is required}"
: "${APP_GROUP_ID:?APP_GROUP_ID is required}"
exec </dev/null
container exec -i "$KC_CONTAINER" /opt/keycloak/bin/kcadm.sh get \
"groups/$APP_GROUP_ID/children" -r "$KC_REALM" \
-q max=500 --fields id,name --format csv --noquotes \
@@ -16,6 +16,8 @@ set -o pipefail
: "${KC_RBAC_ROOT_PATH:?KC_RBAC_ROOT_PATH is required}"
: "${DEPLOYED_APP_IDS:?DEPLOYED_APP_IDS is required}"
exec </dev/null
[ -z "$DEPLOYED_APP_IDS" ] && exit 0
container exec -i "$KC_CONTAINER" /opt/keycloak/bin/kcadm.sh get groups \
@@ -12,6 +12,8 @@ set -eo pipefail
: "${KC_REALM:?KC_REALM is required}"
: "${KC_RBAC_ROOT_PATH:?KC_RBAC_ROOT_PATH is required}"
exec </dev/null
container exec -i "$KC_CONTAINER" /opt/keycloak/bin/kcadm.sh get groups \
-r "$KC_REALM" \
-q max=500 --fields id,path --format csv --noquotes \
@@ -12,6 +12,8 @@ set -o pipefail
: "${LDAP_COMPONENT_ID:?LDAP_COMPONENT_ID is required}"
: "${MAPPER_ID:?MAPPER_ID is required}"
exec </dev/null
container exec -i "$KC_CONTAINER" /opt/keycloak/bin/kcadm.sh create \
"user-storage/$LDAP_COMPONENT_ID/mappers/$MAPPER_ID/sync?direction=fedToKeycloak" \
-r "$KC_REALM" 2>&1
@@ -23,6 +23,8 @@ set -o pipefail
: "${PAYLOAD_FILE:?PAYLOAD_FILE is required}"
[ -r "$PAYLOAD_FILE" ] || { echo "PAYLOAD_FILE not readable: $PAYLOAD_FILE" >&2; exit 1; }
exec </dev/null
container exec -i "$KC_CONTAINER" /opt/keycloak/bin/kcadm.sh get components \
-r "$KC_REALM" \
-q "parentId=$LDAP_COMPONENT_ID" \
@@ -16,6 +16,8 @@ set -o pipefail
: "${KC_REALM:?KC_REALM is required}"
: "${GROUP_PATH:?GROUP_PATH is required}"
exec </dev/null
kc() { container exec -i "$KC_CONTAINER" /opt/keycloak/bin/kcadm.sh "$@"; }
_uuid_re='[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
@@ -23,6 +23,8 @@ set -euo pipefail
KEYCLOAK_EMAIL_VERIFIED="${KEYCLOAK_EMAIL_VERIFIED:-true}"
exec </dev/null
${KEYCLOAK_EXEC_CONTAINER} sh -lc "
set -euo pipefail
@@ -20,6 +20,8 @@ set -euo pipefail
: "${KEYCLOAK_DISPLAY_NAME?missing KEYCLOAK_DISPLAY_NAME}"
: "${KEYCLOAK_DISPLAY_NAME_HTML?missing KEYCLOAK_DISPLAY_NAME_HTML}"
exec </dev/null
MARK_PREFIX='<div class="kc-logo-text"><span>'
MARK_SUFFIX='</span></div>'
EMPTY='""'