diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..c9e353260 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.venv +.git +dist +build +__pycache__ +*.pyc +.pytest_cache +*.zip +*.spec +imgs +dist_assets diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..a8c30ff94 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . +EXPOSE 9123 + +CMD ["python", "pyfa_server.py"] diff --git a/build.sh b/build.sh index 2e196b79b..2ede71aa8 100644 --- a/build.sh +++ b/build.sh @@ -23,10 +23,30 @@ rm -rf build dist echo "Building binary with PyInstaller..." uv run pyinstaller pyfa.spec -# Headless CLI exe (console) into main dist folder -if [ -f dist/pyfa_headless/pyfa-headless.exe ]; then - cp dist/pyfa_headless/pyfa-headless.exe dist/pyfa/ +# Sim server exe (console) into main dist folder +if [ -f dist/pyfa_server/pyfa-server.exe ]; then + cp dist/pyfa_server/pyfa-server.exe dist/pyfa/ +fi + +# Docker image (Python server) +DOCKER_REPO="${DOCKER_REPO:-docker.site.quack-lab.dev}" +IMAGE_NAME="${IMAGE_NAME:-pyfa-server}" +COMMIT_SHA=$(git rev-parse --short HEAD) +IMAGE_BASE="${DOCKER_REPO}/${IMAGE_NAME}" + +echo "" +echo "Building Docker image..." +docker build -t "${IMAGE_BASE}:${COMMIT_SHA}" . + +docker tag "${IMAGE_BASE}:${COMMIT_SHA}" "${IMAGE_BASE}:latest" + +TAGS=$(git tag --points-at HEAD 2>/dev/null || true) +if [ -n "$TAGS" ]; then + while IFS= read -r tag; do + [ -n "$tag" ] && docker tag "${IMAGE_BASE}:${COMMIT_SHA}" "${IMAGE_BASE}:${tag}" + done <<< "$TAGS" fi echo "" -echo "Build complete! dist/pyfa/pyfa.exe (GUI), dist/pyfa/pyfa-headless.exe (HTTP server POST /simulate :9123)" +echo "Build complete! dist/pyfa/pyfa.exe (GUI), dist/pyfa/pyfa-server.exe (POST /simulate :9123)" +echo "Docker image: ${IMAGE_BASE}:${COMMIT_SHA} (and :latest)" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..717346420 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,5 @@ +services: + pyfa-server: + build: . + ports: + - "9123:9123" diff --git a/pyfa.spec b/pyfa.spec index 93350e4f9..e991ef281 100644 --- a/pyfa.spec +++ b/pyfa.spec @@ -79,7 +79,7 @@ a = Analysis(['pyfa.py'], win_private_assemblies=False, cipher=block_cipher) -a_headless = Analysis(['pyfa_headless.py'], +a_server = Analysis(['pyfa_server.py'], pathex=pathex, binaries=[], datas=added_files, @@ -92,7 +92,7 @@ a_headless = Analysis(['pyfa_headless.py'], cipher=block_cipher) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) -pyz_headless = PYZ(a_headless.pure, a_headless.zipped_data, cipher=block_cipher) +pyz_server = PYZ(a_server.pure, a_server.zipped_data, cipher=block_cipher) exe = EXE( pyz, @@ -108,12 +108,12 @@ exe = EXE( contents_directory='app', ) -# Headless: server only. POST /simulate on port 9123. -exe_headless = EXE( - pyz_headless, - a_headless.scripts, +# Sim server. POST /simulate on port 9123. +exe_server = EXE( + pyz_server, + a_server.scripts, exclude_binaries=True, - name='pyfa-headless', + name='pyfa-server', debug=debug, strip=False, upx=upx, @@ -131,14 +131,14 @@ coll = COLLECT( name='pyfa', ) -coll_headless = COLLECT( - exe_headless, - a_headless.binaries, - a_headless.zipfiles, - a_headless.datas, +coll_server = COLLECT( + exe_server, + a_server.binaries, + a_server.zipfiles, + a_server.datas, strip=False, upx=upx, - name='pyfa_headless', + name='pyfa_server', ) if platform.system() == 'Darwin': diff --git a/pyfa_headless.py b/pyfa_headless.py deleted file mode 100644 index 04cc7fbfe..000000000 --- a/pyfa_headless.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python3 -# Headless sim daemon only. POST /simulate with JSON body. -from scripts.pyfa_cli_stats import _run_http_server - -_run_http_server(9123, None) diff --git a/pyfa_server.py b/pyfa_server.py new file mode 100644 index 000000000..8f51d3486 --- /dev/null +++ b/pyfa_server.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +# Sim server. POST /simulate with JSON body. +from scripts.pyfa_sim import _run_http_server + +_run_http_server(9123, None) diff --git a/release.sh b/release.sh index 13a67cfc1..54b2c9bdb 100644 --- a/release.sh +++ b/release.sh @@ -57,4 +57,20 @@ curl -X POST \ rm "${ZIP}" -echo "Release complete! ${ZIP} uploaded to ${TAG}" +# Push Docker image +DOCKER_REPO="${DOCKER_REPO:-docker.site.quack-lab.dev}" +IMAGE_NAME="${IMAGE_NAME:-pyfa-server}" +COMMIT_SHA=$(git rev-parse --short HEAD) +IMAGE_BASE="${DOCKER_REPO}/${IMAGE_NAME}" + +echo "Pushing Docker image..." +docker push "${IMAGE_BASE}:${COMMIT_SHA}" +docker push "${IMAGE_BASE}:latest" +TAGS=$(git tag --points-at HEAD 2>/dev/null || true) +if [ -n "$TAGS" ]; then + while IFS= read -r tag; do + [ -n "$tag" ] && docker push "${IMAGE_BASE}:${tag}" + done <<< "$TAGS" +fi + +echo "Release complete! ${ZIP} uploaded to ${TAG}, Docker image pushed" diff --git a/scripts/pyfa_cli_stats.py b/scripts/pyfa_sim.py similarity index 100% rename from scripts/pyfa_cli_stats.py rename to scripts/pyfa_sim.py diff --git a/tests/test_pyfa_cli_stats.py b/tests/test_pyfa_sim.py similarity index 89% rename from tests/test_pyfa_cli_stats.py rename to tests/test_pyfa_sim.py index 03df61e30..4eefde3b2 100644 --- a/tests/test_pyfa_cli_stats.py +++ b/tests/test_pyfa_sim.py @@ -1,4 +1,3 @@ -import json import os import sys import tempfile @@ -7,7 +6,7 @@ import tempfile script_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.append(os.path.realpath(os.path.join(script_dir, ".."))) -from scripts import pyfa_cli_stats # noqa: E402 +from scripts import pyfa_sim # noqa: E402 ISHTAR_SPIDER_FIT = """[Ishtar, Spider] @@ -41,7 +40,7 @@ Berserker II x5 def test_ishtar_spider_remote_armor_reps(): payload = {"fit": ISHTAR_SPIDER_FIT} with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp: - data = pyfa_cli_stats.compute_stats(payload, tmp) + data = pyfa_sim.compute_stats(payload, tmp) armor_rps = data["remote_reps_outgoing"]["current"]["armor"] assert armor_rps is not None assert int(round(armor_rps)) == 171