From e563380f282cc9858d772e1ced09ef2cdcbcf05e Mon Sep 17 00:00:00 2001 From: momentum dev Date: Thu, 9 Jul 2026 13:55:24 -0400 Subject: [PATCH] TrueNAS SCALE deployment: GHCR image publishing + pull-only compose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TrueNAS custom apps cannot build: the main compose's 'build: .' (plus relative .env/bind mounts) fails app 'up'. Added a GitHub Actions workflow publishing ghcr.io/fez5587/momentum-app on every master push, and deploy/docker/truenas-compose.yml — a pull-only variant with dataset host-paths, inline environment (secrets as placeholders, current validated strategy knobs pre-filled), and the same healthchecks. Validated with docker compose config. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/docker.yml | 34 +++++++++ deploy/docker/truenas-compose.yml | 115 ++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 .github/workflows/docker.yml create mode 100644 deploy/docker/truenas-compose.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..be714be --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,34 @@ +name: docker-image + +# Build + push the app image to GHCR on every master push, so pull-only +# hosts (TrueNAS SCALE custom apps) can deploy without a repo checkout. +on: + push: + branches: [master] + paths: + - "Dockerfile" + - "requirements.txt" + - "**.py" + - ".github/workflows/docker.yml" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + ghcr.io/fez5587/momentum-app:latest + ghcr.io/fez5587/momentum-app:${{ github.sha }} diff --git a/deploy/docker/truenas-compose.yml b/deploy/docker/truenas-compose.yml new file mode 100644 index 0000000..231121e --- /dev/null +++ b/deploy/docker/truenas-compose.yml @@ -0,0 +1,115 @@ +# Momentum stack for TrueNAS SCALE (Apps -> Discover Apps -> Custom App -> YAML). +# +# PULL-ONLY: TrueNAS custom apps cannot `build:` — the image comes from GHCR +# (published by .github/workflows/docker.yml on every master push). Make the +# ghcr.io/fez5587/momentum-app package PUBLIC once (GitHub -> Packages -> +# momentum-app -> Package settings -> Change visibility), or add registry +# credentials in TrueNAS (Apps -> Settings -> Manage Container Images). +# +# BEFORE DEPLOYING: +# 1. Create a dataset for the stack, e.g. /mnt//apps/momentum +# with subdirs: data/ pgdata/ +# 2. Replace /mnt/tank/apps/momentum below with your dataset path. +# 3. Fill every in the environment block (these are the secrets +# you wanted to manage from a protected space — TrueNAS stores the YAML +# server-side, or mount an env file from a restricted dataset instead). +# +# TZ is America/New_York: the loop's market clock thinks in ET. + +x-app: &app + image: ghcr.io/fez5587/momentum-app:latest + environment: &app-env + TZ: America/New_York + # ---- infra + DATABASE_URL: postgresql://momentum:@postgres:5432/momentum + PG_SKIP_SCHEMA: "0" + OLLAMA_HOST: http://192.168.1.9:11434 + ALPACA_DATA_FEED: sip + # ---- secrets + ALPACA_API_KEY: + ALPACA_SECRET_KEY: + ALPACA_BASE_URL: https://paper-api.alpaca.markets + TELEGRAM_BOT_TOKEN: + TELEGRAM_CHAT_ID: + YOUTUBE_API_KEY: + # ---- strategy knobs (current validated config, 2026-07-09; full + # inventory with comments: .env.example in the repo) + TRIGGER_GAP_MIN: "10.0" + TRIGGER_GAP_MAX: "35" + TRIGGER_GAP_SQUEEZE_MIN: "150" + TRIGGER_GAP_SQUEEZE_RVOL: "5.0" + TRADING_MIN_ENTRY_GAP_PCT: "0.10" + TRADING_MIN_DAY_VOLUME: "300000" + TRADING_PRICE_MIN: "2.0" + TRADING_PRICE_MAX: "20.0" + TRADING_MIN_QUALITY_SCORE: "0.60" + TRADING_MAX_FRESH_ENTRIES_PER_DAY: "10" + TRADING_ENTRY_CONFIRM_BY_MINUTE: "0" + volumes: + - /mnt/tank/apps/momentum/data:/app/data + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + +services: + postgres: + image: postgres:18 + environment: + POSTGRES_USER: momentum + POSTGRES_PASSWORD: + POSTGRES_DB: momentum + PGDATA: /var/lib/postgresql/data/pgdata + volumes: + - /mnt/tank/apps/momentum/pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U momentum -d momentum"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + + loop: + <<: *app + command: ["python", "-u", "run_live_paper.py", "--no-dashboard"] + healthcheck: + test: ["CMD", "python", "-c", "import datetime,sys; from research.multi_schema import open_research_db; r=open_research_db('market').execute('SELECT max(timestamp) FROM events').fetchall()[0][0]; sys.exit(0 if r and (datetime.datetime.now()-r).total_seconds()<300 else 1)"] + interval: 60s + timeout: 20s + retries: 5 + start_period: 180s + + dashboard: + <<: *app + command: ["python", "-u", "dashboard_api.py"] + environment: + <<: *app-env + DASHBOARD_HOST: "0.0.0.0" + DASHBOARD_PORT: "8765" + ports: + - "8765:8765" + + qabot: + <<: *app + command: ["python", "-u", "telegram_bot.py"] + + transcripts: + <<: *app + command: ["python", "-u", "-m", "youtube_claims.run", "run"] + + scheduler: + <<: *app + command: + - /bin/sh + - -c + - | + echo "[scheduler] up; fires 20:00 ET Mon-Fri" + while true; do + if [ "$$(date +%H:%M)" = "20:00" ] && [ "$$(date +%u)" -le 5 ]; then + python -m research.premarket_eval || echo "[scheduler] eval FAILED" + sleep 120 + fi + sleep 20 + done