#!/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