From 9db594767facea58f98afced8f5ebeef1f2a496f Mon Sep 17 00:00:00 2001 From: bast Date: Sat, 7 Nov 2026 06:32:23 +0100 Subject: [PATCH] Deploy per Terraform/SSH --- .github/workflows/deploy.yml | 105 ++------------ .github/workflows/deploy.yml.k3s | 112 +++++++++++++++ .gitignore | 17 +++ DEPLOY.md | 39 ++++++ deploy.conf | 8 ++ ...roy-tf-force.sh => destroy-tf-force.sh.k3s | 0 main.tf | 131 +++++++----------- main.tf.k3s | 105 ++++++++++++++ mt5-stack.zip | Bin 48778 -> 0 bytes scripts/check-stack.sh | 100 +++++++++++++ scripts/deploy-local.sh | 52 +++++++ 11 files changed, 496 insertions(+), 173 deletions(-) create mode 100644 .github/workflows/deploy.yml.k3s create mode 100644 DEPLOY.md create mode 100644 deploy.conf rename destroy-tf-force.sh => destroy-tf-force.sh.k3s (100%) create mode 100644 main.tf.k3s delete mode 100644 mt5-stack.zip create mode 100755 scripts/check-stack.sh create mode 100755 scripts/deploy-local.sh diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 841ec9e8..8ba7f572 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,23 +1,12 @@ -name: MT5 Helm GitOps Deploy +name: MT5 Stack Deploy on: push: branches: [ "main" ] - tags: [ "*" ] workflow_dispatch: -env: - # Gleiche Registry wie im boday_tf-Projekt, zwei Namen: - # PUSH_REGISTRY: wie der Runner sie erreicht - # PULL_REGISTRY: wie k3s/containerd sie erreicht (registries.yaml, CA unter certs.d) - PUSH_REGISTRY: localhost:3005 - PULL_REGISTRY: registry.local - # Host-Header für Traefik-Ingress des Web-UI (leer = kein Ingress, kein Health-Check per HTTP) - INGRESS_HOST: mt5.local - jobs: - build-and-deploy: - if: false + deploy: runs-on: ubuntu-latest container: catthehacker/ubuntu:act-latest steps: @@ -26,87 +15,21 @@ jobs: # HINWEIS: Terraform-Binary kommt wie bei boday_tf über das globale Runner-Volume (:ro) - - name: Trust Local Nginx Certificate Inside Container + # Nur versionierte Dateien deployen (kein .git, keine Secrets, keine Laufzeitdaten) + - name: Build deploy bundle run: | - mkdir -p /usr/local/share/ca-certificates/ - cp nginx_ssl/nginx.crt /usr/local/share/ca-certificates/nginx-sandbox.crt 2>/dev/null || echo "Nutze Workspace-Zertifikat" - update-ca-certificates + rm -rf .deploy-bundle && mkdir .deploy-bundle + git archive HEAD | tar -x -C .deploy-bundle + if find .deploy-bundle -name bots.env -o -name .env -o -name '*.session' | grep -q .; then + echo "ABBRUCH: bots.env / .env / *.session ist im Repo versioniert"; exit 1 + fi + du -sh .deploy-bundle - # Docker verlangt Image-Namen in Kleinbuchstaben – "MetaTrader5-Docker-Image" würde sonst scheitern - - name: Compute Image Name - run: | - IMAGE_PATH="$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" - echo "IMAGE=${PUSH_REGISTRY}/${IMAGE_PATH}" >> "$GITHUB_ENV" - echo "PULL_IMAGE=${PULL_REGISTRY}/${IMAGE_PATH}" >> "$GITHUB_ENV" - echo "Push: ${PUSH_REGISTRY}/${IMAGE_PATH}" - echo "Pull: ${PULL_REGISTRY}/${IMAGE_PATH}" - - - name: Log in to Local Registry - run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${PUSH_REGISTRY} -u ${{ github.actor }} --password-stdin - - # Ein Build, zwei Tags (das Image ist ~4 GB – doppelt bauen kostet unnötig Zeit) - - name: Build MT5 Image - run: | - docker build --platform linux/amd64 \ - -t ${IMAGE}:${{ github.sha }} \ - -t ${IMAGE}:latest \ - . - - - name: Push MT5 Image - run: | - docker push ${IMAGE}:${{ github.sha }} - docker push ${IMAGE}:latest - - - name: Prepare Kubernetes Access - run: | - echo "${{ secrets.KUBECONFIG_DATA }}" > ./kind_kubeconfig - chmod 600 ./kind_kubeconfig - - # Erster Rollout kann bis 15 Min dauern (Wine + MT5 Installation nach /config) - # init mit Retry, weil der DNS-Resolver sporadisch "server misbehaving" liefert - name: Terraform Deploy env: - # Web-UI-Login (KasmVNC) – wie bisher - TF_VAR_vnc_user: ${{ secrets.MT5_VNC_USER || 'trader' }} - TF_VAR_vnc_password: ${{ secrets.MT5_VNC_PASSWORD }} - # Broker-Login für MT5 – landet als login.ini im Pod und wird beim Start geladen - TF_VAR_mt5_login: ${{ secrets.MT5USER }} - TF_VAR_mt5_password: ${{ secrets.MT5PASSWORD }} - TF_VAR_mt5_server: ${{ secrets.MT5_SERVER }} + TF_VAR_ssh_private_key: ${{ secrets.DEPLOY_SSH_KEY }} + TF_VAR_ssh_host: ${{ vars.DEPLOY_HOST || 'BITMT503' }} run: | terraform --version - for i in 1 2 3; do - terraform init && break - if [ "$i" = "3" ]; then echo "terraform init nach 3 Versuchen fehlgeschlagen"; exit 1; fi - echo "init fehlgeschlagen, Versuch $i – neuer Anlauf in 10s" - sleep 10 - done - terraform apply -auto-approve \ - -var="image_repository=${PULL_IMAGE}" \ - -var="app_version=${{ github.sha }}" \ - -var="ingress_host=${INGRESS_HOST}" - - # KasmVNC antwortet ohne Login mit 401 – daher mit Basic Auth prüfen und auf den Erststart warten - - name: Verify App Health - if: env.INGRESS_HOST != '' - env: - VNC_USER: ${{ secrets.MT5_VNC_USER || 'trader' }} - VNC_PASS: ${{ secrets.MT5_VNC_PASSWORD }} - run: | - for i in $(seq 1 40); do - CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 \ - -u "${VNC_USER}:${VNC_PASS}" -H "Host: ${INGRESS_HOST}" \ - http://local-k3s-platform:80/ || true) - echo "Versuch ${i}: HTTP ${CODE}" - if [ "${CODE}" = "200" ]; then - echo "Erfolg: MetaTrader 5 Web-UI läuft via Helm im GitOps-Standard!" - exit 0 - fi - sleep 15 - done - echo "MT5 Web-UI nach 10 Minuten nicht erreichbar" - exit 1 - - - name: Cleanup Kubeconfig - if: always() - run: rm -f ./kind_kubeconfig + for i in 1 2 3; do terraform init && break; echo "init fehlgeschlagen, Versuch $i"; sleep 10; done + terraform apply -auto-approve diff --git a/.github/workflows/deploy.yml.k3s b/.github/workflows/deploy.yml.k3s new file mode 100644 index 00000000..841ec9e8 --- /dev/null +++ b/.github/workflows/deploy.yml.k3s @@ -0,0 +1,112 @@ +name: MT5 Helm GitOps Deploy + +on: + push: + branches: [ "main" ] + tags: [ "*" ] + workflow_dispatch: + +env: + # Gleiche Registry wie im boday_tf-Projekt, zwei Namen: + # PUSH_REGISTRY: wie der Runner sie erreicht + # PULL_REGISTRY: wie k3s/containerd sie erreicht (registries.yaml, CA unter certs.d) + PUSH_REGISTRY: localhost:3005 + PULL_REGISTRY: registry.local + # Host-Header für Traefik-Ingress des Web-UI (leer = kein Ingress, kein Health-Check per HTTP) + INGRESS_HOST: mt5.local + +jobs: + build-and-deploy: + if: false + runs-on: ubuntu-latest + container: catthehacker/ubuntu:act-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + # HINWEIS: Terraform-Binary kommt wie bei boday_tf über das globale Runner-Volume (:ro) + + - name: Trust Local Nginx Certificate Inside Container + run: | + mkdir -p /usr/local/share/ca-certificates/ + cp nginx_ssl/nginx.crt /usr/local/share/ca-certificates/nginx-sandbox.crt 2>/dev/null || echo "Nutze Workspace-Zertifikat" + update-ca-certificates + + # Docker verlangt Image-Namen in Kleinbuchstaben – "MetaTrader5-Docker-Image" würde sonst scheitern + - name: Compute Image Name + run: | + IMAGE_PATH="$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" + echo "IMAGE=${PUSH_REGISTRY}/${IMAGE_PATH}" >> "$GITHUB_ENV" + echo "PULL_IMAGE=${PULL_REGISTRY}/${IMAGE_PATH}" >> "$GITHUB_ENV" + echo "Push: ${PUSH_REGISTRY}/${IMAGE_PATH}" + echo "Pull: ${PULL_REGISTRY}/${IMAGE_PATH}" + + - name: Log in to Local Registry + run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${PUSH_REGISTRY} -u ${{ github.actor }} --password-stdin + + # Ein Build, zwei Tags (das Image ist ~4 GB – doppelt bauen kostet unnötig Zeit) + - name: Build MT5 Image + run: | + docker build --platform linux/amd64 \ + -t ${IMAGE}:${{ github.sha }} \ + -t ${IMAGE}:latest \ + . + + - name: Push MT5 Image + run: | + docker push ${IMAGE}:${{ github.sha }} + docker push ${IMAGE}:latest + + - name: Prepare Kubernetes Access + run: | + echo "${{ secrets.KUBECONFIG_DATA }}" > ./kind_kubeconfig + chmod 600 ./kind_kubeconfig + + # Erster Rollout kann bis 15 Min dauern (Wine + MT5 Installation nach /config) + # init mit Retry, weil der DNS-Resolver sporadisch "server misbehaving" liefert + - name: Terraform Deploy + env: + # Web-UI-Login (KasmVNC) – wie bisher + TF_VAR_vnc_user: ${{ secrets.MT5_VNC_USER || 'trader' }} + TF_VAR_vnc_password: ${{ secrets.MT5_VNC_PASSWORD }} + # Broker-Login für MT5 – landet als login.ini im Pod und wird beim Start geladen + TF_VAR_mt5_login: ${{ secrets.MT5USER }} + TF_VAR_mt5_password: ${{ secrets.MT5PASSWORD }} + TF_VAR_mt5_server: ${{ secrets.MT5_SERVER }} + run: | + terraform --version + for i in 1 2 3; do + terraform init && break + if [ "$i" = "3" ]; then echo "terraform init nach 3 Versuchen fehlgeschlagen"; exit 1; fi + echo "init fehlgeschlagen, Versuch $i – neuer Anlauf in 10s" + sleep 10 + done + terraform apply -auto-approve \ + -var="image_repository=${PULL_IMAGE}" \ + -var="app_version=${{ github.sha }}" \ + -var="ingress_host=${INGRESS_HOST}" + + # KasmVNC antwortet ohne Login mit 401 – daher mit Basic Auth prüfen und auf den Erststart warten + - name: Verify App Health + if: env.INGRESS_HOST != '' + env: + VNC_USER: ${{ secrets.MT5_VNC_USER || 'trader' }} + VNC_PASS: ${{ secrets.MT5_VNC_PASSWORD }} + run: | + for i in $(seq 1 40); do + CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 \ + -u "${VNC_USER}:${VNC_PASS}" -H "Host: ${INGRESS_HOST}" \ + http://local-k3s-platform:80/ || true) + echo "Versuch ${i}: HTTP ${CODE}" + if [ "${CODE}" = "200" ]; then + echo "Erfolg: MetaTrader 5 Web-UI läuft via Helm im GitOps-Standard!" + exit 0 + fi + sleep 15 + done + echo "MT5 Web-UI nach 10 Minuten nicht erreichbar" + exit 1 + + - name: Cleanup Kubeconfig + if: always() + run: rm -f ./kind_kubeconfig diff --git a/.gitignore b/.gitignore index 380a1908..9c0f67fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,20 @@ trader-data config .env + +# --- Deploy: Secrets und Laufzeitdaten bleiben nur auf dem Server --- +bots.env +.env +*.session +config/ +config.*/ +trader-data/ +# Terraform +.terraform/ +.terraform.lock.hcl +*.tfstate +*.tfstate.* +.deploy-bundle/ +__pycache__/ +*.bak +*.bak-* diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 00000000..cedba8af --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,39 @@ +# Deploy dieses Stacks + +Diese App deployt sich selbst – unabhängig von anderen Apps. + +| Datei | Zweck | +|---|---| +| `deploy.conf` | Kenndaten: Dienstnamen, MCP-Port, Diagnosedatei | +| `main.tf` | Terraform: Sperre → Dateien per SSH kopieren → `scripts/deploy-local.sh` | +| `.github/workflows/deploy.yml` | Forgejo: Bundle mit `git archive` bauen, `terraform apply` | +| `scripts/deploy-local.sh` | Deploy auf dem Server: Sperre, `docker compose up -d --build`, warten | +| `scripts/check-stack.sh` | Statusprüfung: Container, Bridge, Konto, Kurs, Positionen, Trader, Telegram | + +## Deployen + +| Weg | Befehl | +|---|---| +| Push auf `main` | Forgejo-Workflow → Terraform | +| Forgejo manuell | Actions → *MT5 Stack Deploy* → Run workflow | +| Direkt auf dem Server | `cd && scripts/deploy-local.sh` | + +**Sperre:** Ein Deploy bricht ab, solange der Stack offene Positionen hat. Der Trader +hält Positionen nur im Speicher, nach einem Neustart setzt er für sie kein BE mehr. +`scripts/deploy-local.sh --force` übergeht die Sperre bewusst. + +**Nur auf dem Server, nie im Repo:** `config/`, `trader-data/`, `.env`, `bots.env`. +Terraform kopiert nur versionierte Dateien (`git archive`), diese bleiben unberührt. + +**Direkt auf dem Server geändert?** Dort committen und pushen – sonst dreht der +nächste Deploy die Änderung zurück. + +## Status +```bash +scripts/check-stack.sh # ausführlich +scripts/check-stack.sh -q # nur Fehler (cron) +``` + +## Forgejo einrichten +- Secret `DEPLOY_SSH_KEY`: privater SSH-Key für `bast@BITMT503` +- optional Variable `DEPLOY_HOST`: IP, falls der Runner `BITMT503` nicht auflöst diff --git a/deploy.conf b/deploy.conf new file mode 100644 index 00000000..c9c08f13 --- /dev/null +++ b/deploy.conf @@ -0,0 +1,8 @@ +# deploy.conf – Kenndaten DIESES Stacks (von scripts/*.sh gelesen) +STACK_KEY=hm +STACK_NAME="HM (ch2, PU Prime)" +MCP_PORT=8000 # Host-Port aus docker-compose.yaml (127.0.0.1:8000:8000) +MT5_SVC=mt5 # Dienstnamen aus docker-compose.yaml +MCP_SVC=mt5-mcp +TRADER_SVC=live-trader +DIAG_FILE=trader-data/diagnostics_ch2-HM.json diff --git a/destroy-tf-force.sh b/destroy-tf-force.sh.k3s similarity index 100% rename from destroy-tf-force.sh rename to destroy-tf-force.sh.k3s diff --git a/main.tf b/main.tf index 8aa4cf11..bc469723 100644 --- a/main.tf +++ b/main.tf @@ -1,105 +1,72 @@ -terraform { - required_providers { - kubernetes = { - source = "hashicorp/kubernetes" - version = "~> 2.26" - } - helm = { - source = "hashicorp/helm" - version = "~> 2.12" - } - } +# Deployt DIESEN Stack per SSH auf den Server – unabhängig von anderen Apps. +# Ablauf: Sperre prüfen -> sauberes Bundle (git archive) in den Stack-Ordner +# kopieren -> scripts/deploy-local.sh (docker compose up -d --build + Check). +# config/, trader-data/, .env und bots.env auf dem Server bleiben unberührt, +# weil sie nicht im Repo (und damit nicht im Bundle) sind. - # Eigener State-Pfad, damit sich MT5 und die Webseite nicht in die Quere kommen +terraform { backend "consul" { address = "consul:8500" - path = "terraform/states/mt5_tf" + path = "terraform/states/mt5_hm" # pro Repo eindeutig } } -# --- Variablen --------------------------------------------------------------- - -variable "app_version" { +variable "ssh_host" { type = string - default = "latest" + default = "BITMT503" } -variable "image_repository" { +variable "ssh_user" { type = string - default = "gmag11/metatrader5_vnc" + default = "bast" } -variable "vnc_user" { - type = string - default = "trader" -} - -# Kommt aus TF_VAR_vnc_password (Secret MT5_VNC_PASSWORD) -variable "vnc_password" { +variable "ssh_private_key" { type = string sensitive = true } -variable "ingress_host" { +variable "stack_dir" { type = string - default = "" + default = "/home/bast/build/MetaTrader5-Docker-Image" } -# --- Provider ---------------------------------------------------------------- - -provider "kubernetes" { - config_path = "./kind_kubeconfig" - host = "https://local-k3s-platform:6443" - insecure = true +# Wird vom Workflow per "git archive HEAD" erzeugt – nur versionierte Dateien +variable "bundle_dir" { + type = string + default = ".deploy-bundle" } -provider "helm" { - kubernetes { - config_path = "./kind_kubeconfig" - host = "https://local-k3s-platform:6443" - insecure = true - } -} - -# --- MetaTrader 5 Deployment ------------------------------------------------- - -resource "helm_release" "mt5" { - name = "mt5" - chart = "./charts/mt5" - namespace = "trading" - create_namespace = true - - # Erster Start installiert Wine + MT5 nach /config – das dauert ein paar Minuten - timeout = 900 - wait = true - - set { - name = "image.repository" - value = var.image_repository - } - - set { - name = "image.tag" - value = var.app_version - } - - set { - name = "auth.user" - value = var.vnc_user - } - - set_sensitive { - name = "auth.password" - value = var.vnc_password - } - - set { - name = "ingress.enabled" - value = var.ingress_host != "" ? "true" : "false" - } - - set { - name = "ingress.host" - value = var.ingress_host +resource "terraform_data" "stack" { + # Neu deployen nur, wenn sich eine versionierte Datei geändert hat + triggers_replace = sha1(join("", [ + for f in sort(fileset(var.bundle_dir, "**")) : "${f}:${filesha1("${var.bundle_dir}/${f}")}" + ])) + + connection { + type = "ssh" + host = var.ssh_host + user = var.ssh_user + private_key = var.ssh_private_key + } + + # 1. Sperre mit dem Script, das schon auf dem Server liegt (beim ersten Mal fehlt es) + provisioner "remote-exec" { + inline = [ + "cd '${var.stack_dir}' && if [ -x scripts/deploy-local.sh ]; then scripts/deploy-local.sh --check-flat; else echo 'Erster Deploy – keine Sperre'; fi", + ] + } + + # 2. Versionierte Dateien in den Stack-Ordner kopieren + provisioner "file" { + source = "${var.bundle_dir}/" + destination = var.stack_dir + } + + # 3. Bauen, starten, auf gesunden Stack warten + provisioner "remote-exec" { + inline = [ + "cd '${var.stack_dir}' && chmod +x scripts/*.sh && scripts/deploy-local.sh", + ] } } diff --git a/main.tf.k3s b/main.tf.k3s new file mode 100644 index 00000000..8aa4cf11 --- /dev/null +++ b/main.tf.k3s @@ -0,0 +1,105 @@ +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.26" + } + helm = { + source = "hashicorp/helm" + version = "~> 2.12" + } + } + + # Eigener State-Pfad, damit sich MT5 und die Webseite nicht in die Quere kommen + backend "consul" { + address = "consul:8500" + path = "terraform/states/mt5_tf" + } +} + +# --- Variablen --------------------------------------------------------------- + +variable "app_version" { + type = string + default = "latest" +} + +variable "image_repository" { + type = string + default = "gmag11/metatrader5_vnc" +} + +variable "vnc_user" { + type = string + default = "trader" +} + +# Kommt aus TF_VAR_vnc_password (Secret MT5_VNC_PASSWORD) +variable "vnc_password" { + type = string + sensitive = true +} + +variable "ingress_host" { + type = string + default = "" +} + +# --- Provider ---------------------------------------------------------------- + +provider "kubernetes" { + config_path = "./kind_kubeconfig" + host = "https://local-k3s-platform:6443" + insecure = true +} + +provider "helm" { + kubernetes { + config_path = "./kind_kubeconfig" + host = "https://local-k3s-platform:6443" + insecure = true + } +} + +# --- MetaTrader 5 Deployment ------------------------------------------------- + +resource "helm_release" "mt5" { + name = "mt5" + chart = "./charts/mt5" + namespace = "trading" + create_namespace = true + + # Erster Start installiert Wine + MT5 nach /config – das dauert ein paar Minuten + timeout = 900 + wait = true + + set { + name = "image.repository" + value = var.image_repository + } + + set { + name = "image.tag" + value = var.app_version + } + + set { + name = "auth.user" + value = var.vnc_user + } + + set_sensitive { + name = "auth.password" + value = var.vnc_password + } + + set { + name = "ingress.enabled" + value = var.ingress_host != "" ? "true" : "false" + } + + set { + name = "ingress.host" + value = var.ingress_host + } +} diff --git a/mt5-stack.zip b/mt5-stack.zip deleted file mode 100644 index 31a522bba159abb74c501c833a7c931e1e474c8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48778 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 diff --git a/scripts/check-stack.sh b/scripts/check-stack.sh new file mode 100755 index 00000000..dc47886d --- /dev/null +++ b/scripts/check-stack.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# check-stack.sh – Statusprüfung DIESES Stacks (MT5, MCP, Trader, Telegram) +# Läuft im Stack-Ordner auf dem Server; Werte kommen aus deploy.conf. +# +# scripts/check-stack.sh ausführlich +# scripts/check-stack.sh -q nur Fehler/Warnungen (cron/Deploy) +# Exit-Code: 0 = ok, 1 = mindestens ein Fehler (Warnungen zählen nicht) + +cd "$(dirname "$0")/.." || exit 1 +source ./deploy.conf + +QUIET=0; [ "${1:-}" = "-q" ] && QUIET=1 +FAIL=0 +SYMBOL="${CHECK_SYMBOL:-XAUUSD}" +BASE="http://127.0.0.1:$MCP_PORT" + +if [ -t 1 ]; then G=$'\e[32m'; R=$'\e[31m'; Y=$'\e[33m'; B=$'\e[1m'; N=$'\e[0m'; else G=; R=; Y=; B=; N=; fi +pfx() { [ $QUIET -eq 1 ] && echo "[$STACK_KEY] "; } +ok() { [ $QUIET -eq 1 ] || echo " ${G}OK${N} $*"; } +warn() { echo " ${Y}WARN${N} $(pfx)$*"; } +fail() { echo " ${R}FAIL${N} $(pfx)$*"; FAIL=1; } + +jget() { python3 -c "import json,sys +try: v=json.load(sys.stdin) +except Exception: print('${2:-}'); sys.exit() +for k in '$1'.split('.'): + v = v.get(k) if isinstance(v, dict) else None +print('${2:-}' if v is None else v)"; } + +cid() { docker compose ps -q "$1" 2>/dev/null | head -1; } + +[ $QUIET -eq 1 ] || echo "${B}== $STACK_NAME ==${N}" + +# 1. Container +for svc in "$MT5_SVC" "$MCP_SVC" "$TRADER_SVC"; do + id=$(cid "$svc") + if [ -z "$id" ]; then fail "Dienst $svc: kein Container"; continue; fi + st=$(docker inspect -f '{{.State.Status}}{{if .State.Health}}/{{.State.Health.Status}}{{end}}' "$id") + case "$st" in running/healthy|running) ok "Dienst $svc: $st" ;; *) fail "Dienst $svc: $st" ;; esac +done + +# 2. Bridge + Konto +H=$(curl -s -m 10 "$BASE/health") +[ "$(echo "$H" | jget connected)" = "True" ] && ok "MCP $BASE: Bridge verbunden" \ + || fail "MCP $BASE: keine Verbindung zu MT5 (${H:-keine Antwort})" + +A=$(curl -s -m 15 "$BASE/account") +if [ -n "$(echo "$A" | jget login)" ]; then + ok "Konto $(echo "$A" | jget login) @ $(echo "$A" | jget server): Balance $(echo "$A" | jget balance)" \ + "Equity $(echo "$A" | jget equity) $(echo "$A" | jget currency)" +else + fail "Konto: $(echo "$A" | jget detail "${A:-keine Antwort}")" +fi + +# 3. Kurs +T=$(curl -s -m 15 "$BASE/tick/$SYMBOL") +if [ -n "$(echo "$T" | jget bid)" ]; then + ok "$SYMBOL → $(echo "$T" | jget symbol): bid $(echo "$T" | jget bid) ask $(echo "$T" | jget ask)" +else + fail "Kurs $SYMBOL: $(echo "$T" | jget detail "${T:-keine Antwort}")" +fi + +# 4. Positionen +P=$(curl -s -m 15 "$BASE/positions" | jget count) +if [ -z "$P" ]; then fail "Positionen: keine Antwort" +elif [ "$P" -gt 0 ]; then warn "Offene Positionen: $P" +else ok "Offene Positionen: 0"; fi + +# 5. Trader-Lebenszeichen (Docker-Zeitstempel = UTC, unabhängig von Zeitzonen) +TID=$(cid "$TRADER_SVC") +if [ -n "$TID" ]; then + LAST=$(docker logs --timestamps --since 10m "$TID" 2>&1 | grep -E "\[(LIVE|DRY)\] Balance" | tail -1) + if [ -n "$LAST" ]; then + AGE=$(( $(date +%s) - $(date -d "$(echo "$LAST" | awk '{print $1}')" +%s 2>/dev/null || echo 0) )) + MODE=$(echo "$LAST" | grep -oE "LIVE|DRY" | head -1) + [ "$AGE" -le 120 ] && ok "Trader lebt ($MODE), letztes Lebenszeichen vor ${AGE}s" \ + || fail "Trader: letztes Lebenszeichen vor ${AGE}s" + else + fail "Trader: keine Balance-Zeile in den letzten 10 min" + fi + + # 6. Telegram + START=$(docker inspect -f '{{.State.StartedAt}}' "$TID") + CHAN=$(docker logs --since "$START" "$TID" 2>&1 | grep -oE "Active channels: [^|]*" | tail -1) + [ -n "$CHAN" ] && ok "Telegram: $CHAN" || fail "Telegram: keine 'Active channels'-Meldung seit Start" + ERRS=$(docker logs --since 60m "$TID" 2>&1 \ + | grep -ciE "AUTH_KEY|EOFError|SessionPasswordNeeded|PhoneNumber|Traceback|disconnected|server closed") + [ "$ERRS" -gt 0 ] && warn "Telegram/Trader: $ERRS Fehlerzeile(n) in der letzten Stunde" +fi + +if [ -f "$DIAG_FILE" ]; then + DAGE=$(( $(date +%s) - $(stat -c %Y "$DIAG_FILE") )) + [ "$DAGE" -le 900 ] && ok "Diagnose: vor ${DAGE}s geschrieben, $(jget total_messages < "$DIAG_FILE") Nachricht(en)" \ + || fail "Diagnose: seit ${DAGE}s nicht geschrieben ($DIAG_FILE)" +else + warn "Diagnose: $DIAG_FILE fehlt" +fi + +[ $QUIET -eq 1 ] || { [ $FAIL -eq 0 ] && echo "${G}${B}Alles in Ordnung.${N}" || echo "${R}${B}Fehler – siehe oben.${N}"; } +exit $FAIL diff --git a/scripts/deploy-local.sh b/scripts/deploy-local.sh new file mode 100755 index 00000000..9b784470 --- /dev/null +++ b/scripts/deploy-local.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# deploy-local.sh – Deploy DIESES Stacks, ausgeführt im Stack-Ordner auf dem Server. +# Wird von Terraform aufgerufen, funktioniert aber genauso von Hand: +# +# scripts/deploy-local.sh Sperre prüfen, bauen/starten, auf gesund warten +# scripts/deploy-local.sh --check-flat nur die Sperre prüfen (Terraform vor dem Kopieren) +# scripts/deploy-local.sh --force Sperre ignorieren +# +# Sperre: Der Trader hält offene Positionen nur im Speicher. Ein Neustart mitten +# im Trade = für diese Position kein BE und kein CANCEL mehr. + +cd "$(dirname "$0")/.." || exit 1 +source ./deploy.conf + +check_flat() { + local n + n=$(curl -s -m 10 "http://127.0.0.1:$MCP_PORT/positions" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["count"])' 2>/dev/null || echo "?") + if [ "$n" = "0" ]; then + echo "[$STACK_KEY] Keine offenen Positionen – Deploy erlaubt." + return 0 + fi + # Stack (noch) nicht gestartet -> nichts, was verloren gehen kann + if [ "$n" = "?" ] && [ -z "$(docker compose ps -q "$TRADER_SVC" 2>/dev/null)" ]; then + echo "[$STACK_KEY] Trader läuft nicht – Deploy erlaubt." + return 0 + fi + echo "[$STACK_KEY] ABBRUCH: $n offene Position(en) bzw. MCP nicht erreichbar." + echo " Warten bis flat, oder bewusst: scripts/deploy-local.sh --force" + return 1 +} + +case "${1:-}" in + --check-flat) check_flat; exit $? ;; + --force) echo "[$STACK_KEY] --force: Sperre übersprungen" ;; + *) check_flat || exit 1 ;; +esac + +chmod +x scripts/*.sh 2>/dev/null +echo "[$STACK_KEY] docker compose up -d --build" +docker compose up -d --build || exit 1 + +echo "[$STACK_KEY] Warte auf gesunden Stack (max. 10 min)..." +for i in $(seq 1 20); do + if scripts/check-stack.sh -q >/dev/null 2>&1; then + scripts/check-stack.sh + exit 0 + fi + sleep 30 +done +scripts/check-stack.sh -q +exit 1