#!/usr/bin/env bash # aptly-push — upload one or more .deb/.dsc/.changes files into an aptly local # repo and (by default) refresh the publish that serves it. Works unchanged # against every security.preset in the Helm chart's matrix: pass credentials # via APTLY_USER/APTLY_PASSWORD, APTLY_TOKEN, or neither for an open repo. # # Usage: # aptly-push --repo stable [--prefix ""] [--distribution stable] \ # [--no-publish] [--no-force-replace] [--no-sign] FILE.deb [FILE2.deb ...] # # Signing note: aptly-push runs as an external actor (CI, a developer's # laptop) and generally has no access to the aptly-server pod's GNUPGHOME or # /run/aptly/signing.json — only aptly-init and aptly-reconcile, which run # inside that pod, do. So by default aptly-push sends NO Signing field at all # on the publish-refresh call, letting the server use its own configured # default signer (this is the correct behaviour for a signed repo). Pass # --no-sign (or APTLY_GPG_SIGN=false) only when you know the target repo is # genuinely unsigned (aptly.gpg.enabled: false) — otherwise the update call # will fail with a clear "no GPG key" error rather than silently publishing # unsigned. # # Env (all overridable by the matching flag): # APTLY_URL (required, from lib/common.sh) APTLY_REPO APTLY_PREFIX APTLY_DISTRIBUTION APTLY_GPG_SIGN set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=rootfs/usr/local/bin/lib/common.sh source "${SCRIPT_DIR}/lib/common.sh" require_cmd curl jq REPO="${APTLY_REPO:-}" PREFIX="${APTLY_PREFIX:-}" DISTRIBUTION="${APTLY_DISTRIBUTION:-}" DO_PUBLISH=true FORCE_REPLACE=true SIGN="${APTLY_GPG_SIGN:-true}" FILES=() while [[ $# -gt 0 ]]; do case "$1" in --repo) REPO="$2"; shift 2 ;; --prefix) PREFIX="$2"; shift 2 ;; --distribution) DISTRIBUTION="$2"; shift 2 ;; --no-publish) DO_PUBLISH=false; shift ;; --no-force-replace) FORCE_REPLACE=false; shift ;; --no-sign) SIGN=false; shift ;; --) shift; FILES+=("$@"); break ;; -*) die "unknown flag: $1" ;; *) FILES+=("$1"); shift ;; esac done [[ -n "$REPO" ]] || die "--repo (or APTLY_REPO) is required" [[ "${#FILES[@]}" -gt 0 ]] || die "no files given" for f in "${FILES[@]}"; do [[ -f "$f" ]] || die "file not found: $f"; done UPLOAD_DIR="push-$(date +%s)-$$" log "uploading ${#FILES[@]} file(s) into upload directory '${UPLOAD_DIR}'" cleanup() { api DELETE "/api/files/${UPLOAD_DIR}" >/dev/null 2>&1 || true; } trap cleanup EXIT for f in "${FILES[@]}"; do base="$(basename -- "$f")" log " -> ${base}" api POST "/api/files/${UPLOAD_DIR}" -F "file=@${f}" >/dev/null done log "including uploaded files into repo '${REPO}'" qs="forceReplace=$([[ "$FORCE_REPLACE" == "true" ]] && echo 1 || echo 0)" resp="$(api POST "/api/repos/${REPO}/file/${UPLOAD_DIR}?${qs}")" failed="$(jq -r '(.FailedFiles // []) | length' <<<"$resp")" if [[ "$failed" -gt 0 ]]; then jq -r '.FailedFiles[]' <<<"$resp" | while IFS= read -r ff; do log "FAILED: ${ff}"; done jq -r '(.Report.Warnings // [])[]' <<<"$resp" 2>/dev/null | while IFS= read -r w; do log "warning: ${w}"; done die "${failed} file(s) were rejected by the repo — see above" fi jq -r '(.Report.AddedLines // [])[]' <<<"$resp" 2>/dev/null | while IFS= read -r l; do log "added: ${l}"; done log "included successfully into '${REPO}'" if [[ "$DO_PUBLISH" != "true" ]]; then log "skipping publish refresh (--no-publish)" exit 0 fi [[ -n "$DISTRIBUTION" ]] || { warn "no --distribution/APTLY_DISTRIBUTION given, skipping publish refresh"; exit 0; } escaped_prefix="$(api_prefix "$PREFIX")" log "refreshing publish prefix='${PREFIX:-}' distribution='${DISTRIBUTION}'" update_body="{}" [[ "$SIGN" == "false" ]] && update_body='{"Signing":{"Skip":true}}' code_body="$(api_status POST "/api/publish/${escaped_prefix}/${DISTRIBUTION}/update" \ -H 'Content-Type: application/json' -d "$update_body")" code="$(head -1 <<<"$code_body")" body="$(tail -n +2 <<<"$code_body")" if [[ "$code" == "404" ]]; then warn "no publish exists at prefix='${PREFIX:-}' distribution='${DISTRIBUTION}' yet." warn "the package is in the repo but not yet reachable by clients — run aptly-reconcile" \ "(or 'helm upgrade') to create the publish target once, then re-run this push." exit 0 elif [[ "$code" != 2* ]]; then warn "publish refresh failed (HTTP ${code}): ${body}" warn "the package IS in the repo '${REPO}' — only the publish step failed." \ "If this repo is signed, check that the server has a usable signing key" \ "(see docs/security.md); if it is meant to be unsigned, re-run with --no-sign." exit 0 fi task_id="$(jq -r '.ID // empty' <<<"$body")" if ! wait_task "$task_id"; then warn "publish refresh task failed — package is in the repo but not yet published, see task output above" exit 0 fi log "published successfully"