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>
281 lines
8.7 KiB
Bash
281 lines
8.7 KiB
Bash
#!/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."
|