#!/usr/bin/env bash # # Veroeffentlicht dieses Projekt im Deploymentcenter. # # Vorlage zum Kopieren nach scripts/release.sh des eigenen Projekts. # Anzupassen ist nur scripts/release.config.json - dieses Skript selbst # bleibt unveraendert. # # Der Ablauf je Zielplattform: # dotnet publish -r -> pack-and-deploy --platform # # pack-and-deploy uebernimmt Pruefsummen, Dateimanifest, das Fortschreiben der # latest.json und die Anmeldung beim Deploymentcenter. Das hier nachzubauen # waere ein zweiter Ort fuer dieselben Fehler; das Skript orchestriert nur. # # ./scripts/release.sh --version 1.4.3 --changelog "Behebt den Login-Fehler." # ./scripts/release.sh --version 1.5.0 --channel beta --dry-run set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" CONFIG_FILE="${DC_RELEASE_CONFIG:-$SCRIPT_DIR/release.config.json}" VERSION="" CHANNEL="prod" CHANGELOG="" CRITICAL=0 DRY_RUN=0 usage() { sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' exit 0 } while [ $# -gt 0 ]; do case "$1" in --version) VERSION="$2"; shift 2 ;; --channel) CHANNEL="$2"; shift 2 ;; --changelog) CHANGELOG="$2"; shift 2 ;; --critical) CRITICAL=1; shift ;; --dry-run|-n) DRY_RUN=1; shift ;; --help|-h) usage ;; *) echo "Unbekannte Option: $1" >&2; exit 1 ;; esac done # ------------------------------------------------------------- Voraussetzungen command -v dotnet >/dev/null 2>&1 || { echo "FEHLER: dotnet wird gebraucht, ist aber nicht installiert." >&2 exit 1 } [ -f "$CONFIG_FILE" ] || { echo "FEHLER: Konfiguration fehlt: $CONFIG_FILE" >&2 echo " Vorlage kopieren: release.config.example.json -> release.config.json" >&2 exit 1 } # JSON lesen - mit jq, sonst mit Python. # # Bewusst nicht nur jq: Es ist auf den wenigsten Systemen vorinstalliert, und # an einer fehlenden Abhaengigkeit soll die Vorlage nicht scheitern. Python # liegt auf den meisten Entwickler- und CI-Systemen ohnehin bereit. JSON_READER="" if command -v jq >/dev/null 2>&1; then JSON_READER="jq" else # Nicht nur pruefen, ob der Befehl existiert, sondern ob er laeuft: # Windows legt unter WindowsApps einen python3-Platzhalter ab, der # gefunden wird, beim Aufruf aber nur auf den Store verweist. for candidate in python3 python; do if command -v "$candidate" >/dev/null 2>&1 \ && "$candidate" -c "import json" >/dev/null 2>&1; then JSON_READER="$candidate" break fi done fi if [ -z "$JSON_READER" ]; then echo "FEHLER: Zum Lesen von $CONFIG_FILE wird jq oder ein lauffaehiges Python gebraucht." >&2 exit 1 fi # Liest einen Skalar. Pfad in jq-Schreibweise, z. B. .project cfg() { if [ "$JSON_READER" = "jq" ]; then jq -r "$1 // empty" "$CONFIG_FILE" else "$JSON_READER" -c " import json,sys d=json.load(open(sys.argv[1], encoding='utf-8')) for part in sys.argv[2].lstrip('.').split('.'): if not isinstance(d, dict): d=None; break d=d.get(part) if d is None: print('') elif isinstance(d, bool): print('true' if d else 'false') else: print(d) " "$CONFIG_FILE" "$1" fi } # Liest ein Feld mit Zeichenketten, eine je Zeile. cfg_list() { if [ "$JSON_READER" = "jq" ]; then jq -r "$1[]?" "$CONFIG_FILE" else "$JSON_READER" -c " import json,sys d=json.load(open(sys.argv[1], encoding='utf-8')) for part in sys.argv[2].lstrip('.').split('.'): d = d.get(part) if isinstance(d, dict) else None for item in (d or []): print(item) " "$CONFIG_FILE" "$1" fi } PROJECT="$(cfg .project)" CSPROJ_REL="$(cfg .csproj)" BASE_URL="$(cfg .baseUrl)" BASE_URL="${BASE_URL:-https://dc.mhdf.de}" BASE_URL="${BASE_URL%/}" SETUP_REL="$(cfg .setupJson)" SETUP_REL="${SETUP_REL:-setup.json}" MAIN_ASSEMBLY="$(cfg .mainAssembly)" [ -n "$PROJECT" ] || { echo "FEHLER: 'project' fehlt in $CONFIG_FILE" >&2; exit 1; } [ -n "$CSPROJ_REL" ] || { echo "FEHLER: 'csproj' fehlt in $CONFIG_FILE" >&2; exit 1; } CSPROJ="$REPO_ROOT/$CSPROJ_REL" [ -f "$CSPROJ" ] || { echo "FEHLER: Projektdatei nicht gefunden: $CSPROJ" >&2; exit 1; } mapfile -t RUNTIMES < <(cfg_list .runtimes) [ "${#RUNTIMES[@]}" -gt 0 ] || { echo "FEHLER: 'runtimes' ist leer." >&2; exit 1; } SELF_CONTAINED="$(cfg .selfContained)" SINGLE_FILE="$(cfg .publishSingleFile)" # -------------------------------------------------------------------- Version if [ -z "$VERSION" ]; then # Directory.Build.props zuerst: Steht nur in einem von mehreren # Projekten, laufen die Angaben auseinander - und pack-and-deploy bricht # dann zu Recht mit einem Versionskonflikt ab. for candidate in "$REPO_ROOT/Directory.Build.props" "$CSPROJ"; do [ -f "$candidate" ] || continue VERSION="$(sed -n 's:.*\s*\([^<]*\)\s*.*:\1:p' "$candidate" | head -1 | tr -d '[:space:]')" [ -n "$VERSION" ] && break done [ -n "$VERSION" ] || { echo "FEHLER: Keine gefunden. Bitte --version angeben." >&2 exit 1 } echo "Version aus dem Projekt gelesen: $VERSION" fi CHANGELOG="${CHANGELOG:-Release v$VERSION}" # ------------------------------------------------------------------- Werkzeug TOOL_DIR="$REPO_ROOT/.dc-tools" PACK="$TOOL_DIR/pack-and-deploy" fetch_tool() { [ -x "$PACK" ] && return 0 case "$(uname -m)" in x86_64|amd64) rid="linux-x64" ;; aarch64|arm64) rid="linux-arm64" ;; *) echo "FEHLER: Nicht unterstuetzte Architektur $(uname -m)" >&2; exit 1 ;; esac echo "pack-and-deploy wird geholt ($rid) ..." mkdir -p "$TOOL_DIR" tmp="$(mktemp)" curl -fsSL "$BASE_URL/installer/pack-and-deploy-$rid" -o "$tmp" expected="$(curl -fsSL "$BASE_URL/installer/pack-and-deploy-$rid.sha256" | tr -d ' \t\r\n')" if command -v sha256sum >/dev/null 2>&1; then actual="$(sha256sum "$tmp" | cut -d' ' -f1)" else actual="$(shasum -a 256 "$tmp" | cut -d' ' -f1)" fi if [ "$actual" != "$expected" ]; then rm -f "$tmp" echo "FEHLER: Pruefsumme stimmt nicht." >&2 echo " erwartet: $expected" >&2 echo " erhalten: $actual" >&2 exit 1 fi chmod +x "$tmp" mv "$tmp" "$PACK" echo " Pruefsumme in Ordnung." } fetch_tool # --------------------------------------------------------------------- Ablauf echo echo "Projekt : $PROJECT" echo "Version : $VERSION" echo "Kanal : $CHANNEL" echo "Plattform : ${RUNTIMES[*]}" [ "$DRY_RUN" -eq 1 ] && echo "(Probelauf - es wird nichts hochgeladen)" echo FAILED=0 for rid in "${RUNTIMES[@]}"; do echo "=== $rid ===" PUBLISH_DIR="$REPO_ROOT/artifacts/publish/$rid" if [ "$DRY_RUN" -eq 0 ]; then # Sauber neu bauen: Reste einer vorherigen Laufzeitkennung wuerden # sonst mit ins Paket wandern. rm -rf "$PUBLISH_DIR" publish_args=(publish "$CSPROJ" -c Release -r "$rid" -o "$PUBLISH_DIR" --nologo) if [ "$SELF_CONTAINED" = "true" ]; then publish_args+=(--self-contained true) else publish_args+=(--self-contained false) fi [ "$SINGLE_FILE" = "true" ] && publish_args+=(-p:PublishSingleFile=true) dotnet "${publish_args[@]}" # setup.json mitliefern, damit der Installer weiss, was einzurichten ist. if [ -f "$REPO_ROOT/$SETUP_REL" ]; then cp "$REPO_ROOT/$SETUP_REL" "$PUBLISH_DIR/setup.json" echo " setup.json mitgenommen" fi pack_args=( --project "$PROJECT" --version "$VERSION" --channel "$CHANNEL" --platform "$rid" --publish-dir "$PUBLISH_DIR" --changelog "$CHANGELOG" ) [ "$CRITICAL" -eq 1 ] && pack_args+=(--critical) [ -n "$MAIN_ASSEMBLY" ] && pack_args+=(--main-assembly "$MAIN_ASSEMBLY") set +e "$PACK" "${pack_args[@]}" code=$? set -e case "$code" in 0) echo " -> vollstaendig veroeffentlicht" ;; 1) # Konfigurationsfehler oder Versionskonflikt: die weiteren # Plattformen wuerden genauso scheitern. echo "FEHLER: Konfigurations- oder Versionsfehler - Abbruch." >&2 exit 1 ;; 2) echo " -> TEILWEISE: Upload oder Registrierung fehlgeschlagen"; FAILED=1 ;; *) echo " -> unerwarteter Rueckgabewert $code"; FAILED=1 ;; esac fi echo done if [ "$FAILED" -ne 0 ]; then echo "Nicht alle Plattformen sind durchgelaufen. Vor einem erneuten Versuch pruefen," >&2 echo "ob die bereits hochgeladenen Dateien konsistent sind." >&2 exit 2 fi echo "Fertig. $PROJECT $VERSION ist im Kanal $CHANNEL verfuegbar."