From 320c6bbe959990d50cf380279d0b8f773ebc53d1 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Sat, 23 Aug 2025 17:17:03 +0300 Subject: [PATCH] Add production deployment --- .github/workflows/deploy.yml | 40 ++++++++++++++++++++++++++++++++ deployment/.dockerignore | 31 +++++++++++++++++++++++++ deployment/Dockerfile.backend | 19 +++++++++++++++ deployment/docker-compose.yml | 25 ++++++++++++++++++++ deployment/fromchat.service | 32 +++++++++++++++++++++++++ deployment/frontend/Dockerfile | 26 +++++++++++++++++++++ deployment/frontend/package.json | 12 ++++++++++ deployment/frontend/server.js | 22 ++++++++++++++++++ frontend/src/config.ts | 2 +- frontend/src/css/style.scss | 1 - frontend/src/websocket.ts | 7 +++++- 11 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 deployment/.dockerignore create mode 100644 deployment/Dockerfile.backend create mode 100644 deployment/docker-compose.yml create mode 100644 deployment/fromchat.service create mode 100644 deployment/frontend/Dockerfile create mode 100644 deployment/frontend/package.json create mode 100644 deployment/frontend/server.js diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..bda6dc3 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,40 @@ +# Simple workflow for deploying to the self-hosted server +name: Deploy to server + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + workflow_dispatch: + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + runs-on: self-hosted + env: + HOME: "/root" + environment: + name: production + url: https://fromchat.toolbox-io.ru + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Build container + run: | + cd deployment + docker compose build + - name: Set up the service + run: | + cp -f deployment/fromchat.service /etc/systemd/system/fromchat.service + systemctl daemon-reload + - name: Start the server + run: | + if ! systemctl restart fromchat && sleep 10 && systemctl status fromchat; then + journalctl --no-pager -xeu fromchat + exit 1 + fi \ No newline at end of file diff --git a/deployment/.dockerignore b/deployment/.dockerignore new file mode 100644 index 0000000..4f57ecb --- /dev/null +++ b/deployment/.dockerignore @@ -0,0 +1,31 @@ +# Node.js +node_modules +npm-debug.log +.env +.idea +.vscode + +# Python +__pycache__/ +*.pyc +.pytest_cache/ +.mypy_cache/ +.ipynb_checkpoints +.venv +venv/ + +# Git +.git +.gitignore + +# macOS +.DS_Store + +# Common +# Exclude editor and OS files, as well as test results +/dist +/build +/coverage +/test_results/ + +data \ No newline at end of file diff --git a/deployment/Dockerfile.backend b/deployment/Dockerfile.backend new file mode 100644 index 0000000..bd41ac7 --- /dev/null +++ b/deployment/Dockerfile.backend @@ -0,0 +1,19 @@ +FROM python:3.12 AS builder + +WORKDIR /app +RUN python3 -m venv .venv +COPY backend/requirements.txt . +RUN ./.venv/bin/pip3 install -r requirements.txt + +FROM python:3.12-slim AS runtime + +WORKDIR /app +RUN useradd -u 1000 app && \ + chown -R app /app +USER app + +COPY --chown=app backend . +COPY --from=builder --chown=app /app/.venv .venv +RUN mkdir -p /app/data + +ENTRYPOINT exec ./.venv/bin/fastapi run --port ${PORT:-8300} main.py \ No newline at end of file diff --git a/deployment/docker-compose.yml b/deployment/docker-compose.yml new file mode 100644 index 0000000..3c470bd --- /dev/null +++ b/deployment/docker-compose.yml @@ -0,0 +1,25 @@ +services: + backend: + build: + dockerfile: deployment/Dockerfile.backend + context: .. + environment: + PORT: 8300 + ports: + - "8300:8300" + volumes: + - "data:/app/data" + + frontend: + build: + dockerfile: deployment/frontend/Dockerfile + context: .. + environment: + PORT: 8301 + BACKEND_HOST: http://backend:8300 + ports: + - "8301:8301" + +volumes: + data: + name: fromchat-data \ No newline at end of file diff --git a/deployment/fromchat.service b/deployment/fromchat.service new file mode 100644 index 0000000..7524770 --- /dev/null +++ b/deployment/fromchat.service @@ -0,0 +1,32 @@ +[Unit] +Description=FromChat server +After=multi-user.target +Wants=network-online.target +After=network-online.target +StartLimitIntervalSec=60 +StartLimitBurst=3 + +[Service] +Type=simple +User=root +Group=root +ExecStart=/bin/docker compose up +ExecStop=/bin/docker compose down +WorkingDirectory=/home/denis0001-dev/actions-runner/_work/FromChat/FromChat/deployment +Restart=always +RestartSec=10 + +# Security settings +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ReadWritePaths=/var/log + +# Logging +StandardOutput=journal +StandardError=journal +StandardInput=tty-force +SyslogIdentifier=fromchat + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/deployment/frontend/Dockerfile b/deployment/frontend/Dockerfile new file mode 100644 index 0000000..140761f --- /dev/null +++ b/deployment/frontend/Dockerfile @@ -0,0 +1,26 @@ +FROM node:24 AS frontend + +WORKDIR /app +COPY package.json . +RUN npm install --ignore-scripts + +COPY frontend frontend +RUN npm run build + +FROM node:24 AS server + +WORKDIR /server +COPY deployment/frontend/package.json . +RUN npm install +COPY deployment/frontend/ . + +FROM node:24-slim + +WORKDIR /app +COPY --from=frontend /app/frontend/dist . + +WORKDIR /server +COPY --from=server /server . + +ENV STATIC_FILE_PATH=/app +ENTRYPOINT ["npm", "run", "start"] \ No newline at end of file diff --git a/deployment/frontend/package.json b/deployment/frontend/package.json new file mode 100644 index 0000000..de98491 --- /dev/null +++ b/deployment/frontend/package.json @@ -0,0 +1,12 @@ +{ + "name": "frontend-server", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^5.1.0", + "http-proxy-middleware": "^3.0.5" + } +} diff --git a/deployment/frontend/server.js b/deployment/frontend/server.js new file mode 100644 index 0000000..af4c43c --- /dev/null +++ b/deployment/frontend/server.js @@ -0,0 +1,22 @@ +// server.js +const express = require('express'); +const { createProxyMiddleware } = require('http-proxy-middleware'); +const path = require('path'); + +const app = express(); +const port = process.env.PORT || 3000; +const backendHost = process.env.BACKEND_HOST || "http://localhost:8300"; +const file_path = process.env.STATIC_FILE_PATH || "."; + +app.use('/api', createProxyMiddleware({ + target: backendHost, + changeOrigin: true, + pathRewrite: { '^/api': '' }, + ws: true +})); + +app.use(express.static(path.resolve(file_path))); + +app.listen(port, () => { + console.log(`Server launched на http://localhost:${port}`); +}); \ No newline at end of file diff --git a/frontend/src/config.ts b/frontend/src/config.ts index f46c8a0..2f7b204 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -17,7 +17,7 @@ export const API_BASE_URL: string = '/api'; * @type {string} * @constant */ -export const API_FULL_BASE_URL: string = `${location.hostname}:8301/api`; +export const API_FULL_BASE_URL: string = `${location.host}/api`; /** * Application name displayed in UI and document title diff --git a/frontend/src/css/style.scss b/frontend/src/css/style.scss index 3906aeb..dbf20a7 100644 --- a/frontend/src/css/style.scss +++ b/frontend/src/css/style.scss @@ -27,7 +27,6 @@ body { } mdui-dialog { - > *:first-child { margin-block-start: 0; } diff --git a/frontend/src/websocket.ts b/frontend/src/websocket.ts index b514ca0..d3967c4 100644 --- a/frontend/src/websocket.ts +++ b/frontend/src/websocket.ts @@ -17,7 +17,12 @@ import { delay } from "./utils/utils"; * @private */ function create(): WebSocket { - return new WebSocket(`ws://${API_FULL_BASE_URL}/chat/ws`); + let prefix = "ws://"; + if (location.protocol.includes("https")) { + prefix = "wss://"; + } + + return new WebSocket(`${prefix}${API_FULL_BASE_URL}/chat/ws`); } /**