47bd454fa3
- skills/builder: SKILL.md, README.md, references/contract.md (make/publish contract v1), references/registry.md - scripts/check.py: executable contract checker (make dry-run probes, secret scan, push thin-wrapper and script path checks; --build verifies real .deb) - scripts/upload_deb.sh: migrated from deb-publisher, adds project .env auto-load and dirty-worktree publish gate - scripts/publish_docker.sh: migrated from publish-docker-image publish.sh, now env-first (DOCKER_REGISTRY/REPOSITORY/IMAGE_TAG/PLATFORMS), refuses floating latest and multi-platform --load - scripts/verify_deb.sh: metadata/content/sha256 verification with v-prefix normalization - orc: deb+docker stages both route to $builder; routing table, DAGs, README, config untouched stage names; tests updated - ack delivery.md + skiff source-model.md: reference builder - remove skills/deb-publisher and skills/publish-docker-image
165 lines
4.8 KiB
Bash
Executable File
165 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
DEB_SERVER_URL=https://deb.example.com \
|
|
DEB_TOKEN=secret \
|
|
DEB_REPOSITORY=main \
|
|
upload_deb.sh FILE.deb [FILE.deb ...]
|
|
|
|
Options:
|
|
-s SERVER_URL Override DEB_SERVER_URL
|
|
-n REPOSITORY Override DEB_REPOSITORY
|
|
-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
|
|
package, token, and repository_name. Authentication is read only from
|
|
DEB_TOKEN so it is not exposed in the process command line.
|
|
|
|
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.
|
|
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
|
|
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
|
|
fi
|
|
value=${value%\"}; value=${value#\"}; value=${value%\'}; value=${value#\'}
|
|
printf -v "$key" '%s' "$value"
|
|
export "$key"
|
|
done < <(grep -v '^[[:space:]]*$' "$project_root/.env")
|
|
fi
|
|
|
|
server_url=${DEB_SERVER_URL:-}
|
|
repository=${DEB_REPOSITORY:-}
|
|
upload_path=${DEB_UPLOAD_PATH:-/api/v2/upload/package}
|
|
token=${DEB_TOKEN:-}
|
|
|
|
while getopts ":s:n:p:h" option; do
|
|
case "$option" in
|
|
s) server_url=$OPTARG ;;
|
|
n) repository=$OPTARG ;;
|
|
p) upload_path=$OPTARG ;;
|
|
h) usage; exit 0 ;;
|
|
:) echo "Error: -$OPTARG requires a value" >&2; usage >&2; exit 2 ;;
|
|
\?) echo "Error: unknown option -$OPTARG" >&2; usage >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
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
|
|
usage >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Dirty-tree gate: publishing uncommitted content requires explicit opt-in.
|
|
if [[ -n "$project_root" ]] && git -C "$project_root" rev-parse HEAD >/dev/null 2>&1; then
|
|
if [[ ${ALLOW_UNCOMMITTED:-0} != 1 ]] && ! git -C "$project_root" diff-index --quiet HEAD -- 2>/dev/null; then
|
|
echo "Error: working tree has uncommitted changes; refusing to publish." >&2
|
|
echo "Commit first, or set ALLOW_UNCOMMITTED=1 to publish anyway." >&2
|
|
exit 3
|
|
fi
|
|
fi
|
|
|
|
if [[ "$upload_path" != /* ]]; then
|
|
echo "Error: upload path must start with /" >&2
|
|
exit 2
|
|
fi
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
echo "Error: curl is required." >&2
|
|
exit 2
|
|
fi
|
|
|
|
server_url=${server_url%/}
|
|
success_count=0
|
|
fail_count=0
|
|
response_file=
|
|
|
|
cleanup() {
|
|
if [[ -n "$response_file" && -f "$response_file" ]]; then
|
|
rm -f -- "$response_file"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
print_response() {
|
|
local file=$1
|
|
if command -v jq >/dev/null 2>&1 && jq -e . "$file" >/dev/null 2>&1; then
|
|
jq . "$file"
|
|
else
|
|
cat -- "$file"
|
|
fi
|
|
}
|
|
|
|
for package_file in "$@"; do
|
|
if [[ ! -f "$package_file" ]]; then
|
|
echo "Skip: file not found: $package_file" >&2
|
|
fail_count=$((fail_count + 1))
|
|
continue
|
|
fi
|
|
if [[ "$package_file" != *.deb ]]; then
|
|
echo "Skip: not a .deb file: $package_file" >&2
|
|
fail_count=$((fail_count + 1))
|
|
continue
|
|
fi
|
|
if [[ ! -s "$package_file" ]]; then
|
|
echo "Skip: empty file: $package_file" >&2
|
|
fail_count=$((fail_count + 1))
|
|
continue
|
|
fi
|
|
|
|
response_file=$(mktemp)
|
|
echo "Uploading $(basename -- "$package_file") to $server_url (repository $repository)..."
|
|
|
|
http_code=000
|
|
if http_code=$(curl --silent --show-error \
|
|
--output "$response_file" \
|
|
--write-out "%{http_code}" \
|
|
--request POST \
|
|
"$server_url$upload_path" \
|
|
--form "package=@${package_file};type=application/vnd.debian.binary-package" \
|
|
--form "token=${token}" \
|
|
--form "repository_name=${repository}"); then
|
|
:
|
|
else
|
|
echo "Failed (transport error): $(basename -- "$package_file")" >&2
|
|
print_response "$response_file" >&2
|
|
fail_count=$((fail_count + 1))
|
|
cleanup
|
|
response_file=
|
|
continue
|
|
fi
|
|
|
|
if [[ "$http_code" == 200 || "$http_code" == 201 ]]; then
|
|
echo "Success ($http_code): $(basename -- "$package_file")"
|
|
print_response "$response_file"
|
|
success_count=$((success_count + 1))
|
|
else
|
|
echo "Failed ($http_code): $(basename -- "$package_file")" >&2
|
|
print_response "$response_file" >&2
|
|
fail_count=$((fail_count + 1))
|
|
fi
|
|
|
|
cleanup
|
|
response_file=
|
|
done
|
|
|
|
echo "Done. Success: $success_count, Failed: $fail_count"
|
|
if ((fail_count > 0)); then
|
|
exit 1
|
|
fi
|