This commit is contained in:
parent
d505268b30
commit
3f649fca21
10 changed files with 496 additions and 173 deletions
105
.github/workflows/deploy.yml
vendored
105
.github/workflows/deploy.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
112
.github/workflows/deploy.yml.k3s
vendored
Normal file
112
.github/workflows/deploy.yml.k3s
vendored
Normal file
|
|
@ -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
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
|
|
@ -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-*
|
||||
|
|
|
|||
39
DEPLOY.md
Normal file
39
DEPLOY.md
Normal file
|
|
@ -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 <stack-ordner> && 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
|
||||
8
deploy.conf
Normal file
8
deploy.conf
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# deploy.conf – Kenndaten DIESES Stacks (von scripts/*.sh gelesen)
|
||||
STACK_KEY=goldex
|
||||
STACK_NAME="Goldex (ch1, Vantage)"
|
||||
MCP_PORT=8010 # Host-Port aus docker-compose.yaml (127.0.0.1:8010:8000)
|
||||
MT5_SVC=mt5-gx # Dienstnamen aus docker-compose.yaml
|
||||
MCP_SVC=mt5-mcp-gx
|
||||
TRADER_SVC=live-trader-gx
|
||||
DIAG_FILE=trader-data/diagnostics_ch1-Goldex.json
|
||||
131
main.tf
131
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_goldex" # 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-Goldex"
|
||||
}
|
||||
|
||||
# --- 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",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
105
main.tf.k3s
Normal file
105
main.tf.k3s
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
100
scripts/check-stack.sh
Executable file
100
scripts/check-stack.sh
Executable file
|
|
@ -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
|
||||
52
scripts/deploy-local.sh
Executable file
52
scripts/deploy-local.sh
Executable file
|
|
@ -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
|
||||
Loading…
Reference in a new issue