feat(builder): load publish credentials from .env.builder

Keep DEB/Docker publish keys out of the project's .env. Scripts and
check.py --ready only read .env.builder; empty values count as missing.
This commit is contained in:
2026-08-25 16:58:56 +08:00
parent 10d8800f07
commit 681aa9e237
10 changed files with 109 additions and 46 deletions
+10 -6
View File
@@ -43,6 +43,7 @@ FLOATING_TAGS = (":latest", ":stable")
DEB_SHAPE = re.compile(r"^[^_\s]+_[^_\s]+_[^_\s]+\.deb$")
VALID_SCRIPT_NAMES = ("upload_deb.sh", "publish_docker.sh")
BUILDER_MAKEFILE = "makefile.builder"
BUILDER_ENV = ".env.builder"
PASS = "PASS"
FAIL = "FAIL"
@@ -279,12 +280,12 @@ def check_secrets_and_tags(report: Report, project: Path) -> None:
DEB_ENV_KEYS = ("DEB_SERVER_URL", "DEB_TOKEN", "DEB_REPOSITORY")
DOCKER_ENV_KEYS = ("DOCKER_REGISTRY",)
ENV_KEY_LINE = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)=")
ENV_KEY_LINE = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)=(.*)$")
def env_file_keys(project: Path) -> set[str]:
"""Return key names defined in project `.env`. Never return or print values."""
path = project / ".env"
"""Return nonempty key names in `.env.builder`. Never return or print values."""
path = project / BUILDER_ENV
keys: set[str] = set()
if not path.is_file():
return keys
@@ -297,7 +298,10 @@ def env_file_keys(project: Path) -> set[str]:
if not stripped or stripped.startswith("#"):
continue
match = ENV_KEY_LINE.match(stripped)
if match:
if not match:
continue
value = match.group(2).strip().strip("'\"")
if value:
keys.add(match.group(1))
return keys
@@ -364,9 +368,9 @@ def check_ready_env_keys(
lines.extend(
[
"",
"blocks publish, not build. Put keys in the environment or project `.env`:",
f"blocks publish, not build. Put keys in the environment or {BUILDER_ENV}:",
*[f" {key}=" for key in missing],
"Do not commit `.env`. Never print values.",
f"Do not commit {BUILDER_ENV}. Do not put these keys in `.env`. Never print values.",
]
)
report.add(SKIP, 11, "发布环境变量键名(不读取值)", "\n".join(lines))
+8 -7
View File
@@ -6,8 +6,8 @@ usage() {
}
# Build and publish a Docker image with buildx. Configuration comes from the
# environment first (optionally loaded from the project root .env); flags
# override.
# environment first (optionally loaded from the project root .env.builder);
# flags override. Does not read `.env`.
#
# Usage:
# publish_docker.sh [--registry HOST] [--repository PATH] [--tag TAG] \
@@ -30,18 +30,19 @@ usage() {
project_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
# Load project .env without printing values; explicitly exported shell values keep precedence.
if [[ -n "$project_root" && -f "$project_root/.env" ]]; then
# Load .env.builder without printing values; shell values win. Do not read `.env`.
if [[ -n "$project_root" && -f "$project_root/.env.builder" ]]; then
while IFS='=' read -r key value; do
key=${key%%[[:space:]]*}
[[ -z "$key" || "$key" == \#* ]] && continue
if [[ -n "${!key:-}" ]]; then
continue # shell value already set: wins over .env
continue # shell value already set: wins over .env.builder
fi
value=${value%\"}; value=${value#\"}; value=${value%\'}; value=${value#\'}
[[ -z "$value" ]] && continue
printf -v "$key" '%s' "$value"
export "$key"
done < <(grep -v '^[[:space:]]*$' "$project_root/.env")
done < <(grep -v '^[[:space:]]*$' "$project_root/.env.builder")
fi
git_repo_name=
@@ -81,7 +82,7 @@ if [[ -n "$registry" && ( "$registry" == *://* || "$registry" == */* ) ]]; then
fi
if [[ -z "$registry" ]]; then
echo "Error: DOCKER_REGISTRY (or --registry) is required." >&2
echo "Set it in the environment or the project root .env." >&2
echo "Set it in the environment or the project root .env.builder." >&2
usage >&2
exit 2
fi
+10 -9
View File
@@ -15,9 +15,9 @@ Options:
-p UPLOAD_PATH Override DEB_UPLOAD_PATH (default: /api/v2/upload/package)
-h Show help
Environment variables may live in the project root .env; this script walks up
from the current directory, loads it silently (existing shell values win), and
never echoes variable values. The endpoint must accept multipart fields named
Environment variables may live in the project root .env.builder; this script
loads it silently (existing shell values win), never echoes values, and does
not read `.env`. The endpoint must accept multipart fields named
package, token, and repository_name. Authentication is read only from
DEB_TOKEN so it is not exposed in the process command line.
@@ -25,21 +25,22 @@ The working tree must be clean to publish; set ALLOW_UNCOMMITTED=1 to override.
EOF
}
# Locate project root (.git) upward from cwd for .env loading and git checks.
# Locate project root (.git) upward from cwd for .env.builder loading and git checks.
project_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
# Load project .env without printing values; explicitly exported shell values keep precedence.
if [[ -n "$project_root" && -f "$project_root/.env" ]]; then
# Load .env.builder without printing values; shell values win. Do not read `.env`.
if [[ -n "$project_root" && -f "$project_root/.env.builder" ]]; then
while IFS='=' read -r key value; do
key=${key%%[[:space:]]*}
[[ -z "$key" || "$key" == \#* ]] && continue
if [[ -n "${!key:-}" ]]; then
continue # shell value already set: wins over .env
continue # shell value already set: wins over .env.builder
fi
value=${value%\"}; value=${value#\"}; value=${value%\'}; value=${value#\'}
[[ -z "$value" ]] && continue
printf -v "$key" '%s' "$value"
export "$key"
done < <(grep -v '^[[:space:]]*$' "$project_root/.env")
done < <(grep -v '^[[:space:]]*$' "$project_root/.env.builder")
fi
server_url=${DEB_SERVER_URL:-}
@@ -61,7 +62,7 @@ shift $((OPTIND - 1))
if [[ -z "$server_url" || -z "$repository" || -z "$token" || $# -eq 0 ]]; then
echo "Error: DEB_SERVER_URL, DEB_TOKEN, DEB_REPOSITORY, and at least one file are required." >&2
echo "Set them in the environment or the project root .env." >&2
echo "Set them in the environment or the project root .env.builder." >&2
usage >&2
exit 2
fi