Deploymentcenter-Integration (Schritte 0-8 des Konzepts)
Bindet Lizenz, Watchdog, Fehler-Stream, UpdateService und Erstinstallation an
das Deploymentcenter 2.5.1 an. Einbauort ist IBKRTrader.Hosting - den Host
teilen sich Shell und Daemon.
Projekt-Befunde aus dem Konzept vorab bereinigt:
P1 Echte DB-Zugangsdaten als Vorgabewerte in AppSettings -> Platzhalter.
Das alte Passwort steht weiterhin in der Git-Historie und ist als
kompromittiert zu behandeln (Rotation ist Nutzer-Aktion).
P2 AppPaths fiel unter Windows auf /etc/ibkrtrader zurueck, was .NET zu
C:\etc\ibkrtrader aufloest. Jetzt %ProgramData%\IBKRTrader.
P3 Globale Ausnahmebehandler (AppDomain / TaskScheduler) - vorher gab es
keinen Logeintrag, wenn der Prozess unbehandelt wegbrach.
P4 Version einmal zentral in Directory.Build.props statt zweimal hartkodiert.
Bewusste Abweichungen vom DC-Leitfaden, beide fuer ein handelndes System:
- Lizenz-Urteil fuehrt zum Sperrbetrieb (TradingEnabled=false) statt zu
Environment.Exit(1). Keine neuen Einstiege, aber Risiko-, Exit- und
Buchhaltungslogik laufen weiter.
- exitCurrentApp bleibt immer false; der Aufrufer beendet geordnet.
Das SDK haengt als Cross-Repo-ProjectReference am Schwester-Repo
Deploymentcenter (Interim, siehe Konzept 2.2). Damit ist P5 offen: die
Gitea-CI checkt das Schwester-Repo nicht aus und wird rot, bis der Bezug auf
ein NuGet-Paket umgestellt ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
#!/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 <rid> -> pack-and-deploy --platform <rid>
|
||||
#
|
||||
# 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 <Version> 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:.*<Version>\s*\([^<]*\)\s*</Version>.*:\1:p' "$candidate" | head -1 | tr -d '[:space:]')"
|
||||
[ -n "$VERSION" ] && break
|
||||
done
|
||||
|
||||
[ -n "$VERSION" ] || {
|
||||
echo "FEHLER: Keine <Version> 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."
|
||||
Reference in New Issue
Block a user