#!/usr/bin/env bash # Reads one KEY=VALUE out of a .env-style file WITHOUT ever executing the # file as shell. `source`-ing a .env file breaks the moment a value contains # characters bash treats specially — e.g. a Caddy bcrypt hash # ($2a$14$...) looks like positional-parameter expansions ($2, $14, ...) to # bash and blows up under `set -u`. docker compose's own --env-file parser # already treats these files as literal key=value data; this matches that. env_get() { local file="$1" key="$2" line val line="$(grep -m1 -E "^${key}=" "$file" 2>/dev/null || true)" val="${line#*=}" # Strip a trailing \r (a CRLF-saved file — edited on Windows, or via some # SFTP/GUI tools) and any leading/trailing whitespace BEFORE the # quote-detection below. Order matters: a CRLF-terminated quoted value has # its \r land after the closing quote, so the end-with-quote check below # would silently fail to match and the literal quote characters would # leak into the returned value instead of being stripped. Verified # directly: left unstripped, a corrupted DB_NAME value fed into restore.sh's # `DROP DATABASE IF EXISTS \`$DB_NAME_VALUE\`` targets a DIFFERENT # database name than the real one — no error, just a "restore" that # silently doesn't clean the actual target database first. val="${val%$'\r'}" val="${val#"${val%%[![:space:]]*}"}" val="${val%"${val##*[![:space:]]}"}" if [[ "$val" == \"*\" && "$val" == *\" ]]; then val="${val#\"}"; val="${val%\"}" elif [[ "$val" == \'*\' && "$val" == *\' ]]; then val="${val#\'}"; val="${val%\'}" fi printf '%s' "$val" }