init 2
This commit is contained in:
parent
a29c45ee32
commit
6808521be0
7 changed files with 258 additions and 35 deletions
|
|
@ -1,6 +1,9 @@
|
||||||
APP_ENV=dev
|
APP_ENV=dev
|
||||||
APP_HOST=0.0.0.0
|
APP_HOST=0.0.0.0
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
|
APP_PUBLIC_IP=
|
||||||
|
APP_PUBLIC_DOMAIN=
|
||||||
|
APP_BASE_URL=
|
||||||
|
|
||||||
DB_PATH=data/app.db
|
DB_PATH=data/app.db
|
||||||
SESSION_SECRET=change-me
|
SESSION_SECRET=change-me
|
||||||
|
|
@ -22,4 +25,3 @@ EXTERNAL_API_CREATE_URL_WITH_KEY=
|
||||||
|
|
||||||
OPERATOR_CHAT_IDS=
|
OPERATOR_CHAT_IDS=
|
||||||
REQUEST_CATEGORIES=Общая консультация,Техническая проблема,Оплата,Другое
|
REQUEST_CATEGORIES=Общая консультация,Техническая проблема,Оплата,Другое
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,47 +6,29 @@ steps:
|
||||||
image: python:3.11-slim
|
image: python:3.11-slim
|
||||||
commands:
|
commands:
|
||||||
- pip install --no-cache-dir -r requirements.txt
|
- pip install --no-cache-dir -r requirements.txt
|
||||||
- pytest -q
|
- pytest -q tests -p no:cacheprovider
|
||||||
|
|
||||||
docker_build_push:
|
deploy_no_docker:
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
|
||||||
settings:
|
|
||||||
registry: ${CI_REGISTRY}
|
|
||||||
repo: ${CI_REGISTRY_IMAGE}
|
|
||||||
username:
|
|
||||||
from_secret: registry_user
|
|
||||||
password:
|
|
||||||
from_secret: registry_password
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
tags:
|
|
||||||
- ${CI_COMMIT_SHA}
|
|
||||||
- stable
|
|
||||||
when:
|
|
||||||
- branch: [main, master]
|
|
||||||
|
|
||||||
deploy_pull_on_host:
|
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
settings:
|
settings:
|
||||||
host:
|
host:
|
||||||
from_secret: deploy_host
|
from_secret: forgejo_host_ssh
|
||||||
username:
|
username:
|
||||||
from_secret: deploy_user
|
from_secret: forgejo_user_ssh
|
||||||
key:
|
key:
|
||||||
from_secret: deploy_ssh_key
|
from_secret: forgejo_key_ssh
|
||||||
port:
|
port:
|
||||||
from_secret: deploy_port
|
from_secret: forgejo_port_ssh
|
||||||
script:
|
script:
|
||||||
- docker login ${CI_REGISTRY} -u ${REGISTRY_USER} -p ${REGISTRY_PASSWORD}
|
- cd /home/caps/intra_max_bot
|
||||||
- docker pull ${CI_REGISTRY_IMAGE}:stable
|
- git pull --ff-only
|
||||||
- docker rm -f max-support-bot || true
|
- if [ ! -d .venv ]; then python3 -m venv .venv; fi
|
||||||
- docker run -d --name max-support-bot --restart unless-stopped -p 8000:8000 --env-file /opt/max-support/.env -v /opt/max-support/data:/app/data ${CI_REGISTRY_IMAGE}:stable
|
- . .venv/bin/activate
|
||||||
- curl -fsS http://127.0.0.1:8000/health
|
- pip install --no-cache-dir -r requirements.txt
|
||||||
environment:
|
- sudo systemctl restart max-support
|
||||||
REGISTRY_USER:
|
- set -a
|
||||||
from_secret: registry_user
|
- . /home/caps/intra_max_bot/.env
|
||||||
REGISTRY_PASSWORD:
|
- set +a
|
||||||
from_secret: registry_password
|
- curl -fsS http://127.0.0.1:${APP_PORT}/health
|
||||||
when:
|
when:
|
||||||
- branch: [main, master]
|
- branch: [main, master]
|
||||||
|
|
||||||
|
|
|
||||||
130
DEPLOY_VARIANT_A.md
Normal file
130
DEPLOY_VARIANT_A.md
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
# Deploy Guide (Variant A: No Docker Runtime)
|
||||||
|
|
||||||
|
This guide is for deployment from source code on the server:
|
||||||
|
- `git pull` after each push
|
||||||
|
- restart `systemd` service
|
||||||
|
- no Docker runtime
|
||||||
|
|
||||||
|
## 0) What is already prepared in this repo
|
||||||
|
|
||||||
|
- CI pipeline for Variant A: [`.woodpecker.yml`](.woodpecker.yml)
|
||||||
|
- Env template with host/port/public IP/domain vars: [`.env.example`](.env.example)
|
||||||
|
- App runner that uses `APP_HOST`/`APP_PORT`: [`app/run.py`](app/run.py)
|
||||||
|
- `systemd` template: [`deploy/systemd/max-support.service.template`](deploy/systemd/max-support.service.template)
|
||||||
|
- `sudoers` template: [`deploy/sudoers/max-support-restart.template`](deploy/sudoers/max-support-restart.template)
|
||||||
|
- One-shot setup script: [`deploy/scripts/setup_variant_a.sh`](deploy/scripts/setup_variant_a.sh)
|
||||||
|
|
||||||
|
## 1) Create repository and connect CI
|
||||||
|
|
||||||
|
1. Create repository in GitHub/GitLab.
|
||||||
|
2. Push current project to `main`.
|
||||||
|
3. Connect repository to Woodpecker.
|
||||||
|
|
||||||
|
## 2) Prepare server
|
||||||
|
|
||||||
|
Example for Ubuntu/Debian:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y git curl python3 python3-venv python3-pip
|
||||||
|
sudo mkdir -p /opt/max-support
|
||||||
|
sudo chown -R $USER:$USER /opt/max-support
|
||||||
|
git clone <SSH_REPO_URL> /opt/max-support
|
||||||
|
cd /opt/max-support
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3) Create server `.env`
|
||||||
|
|
||||||
|
Create `/opt/max-support/.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
APP_ENV=prod
|
||||||
|
APP_HOST=0.0.0.0
|
||||||
|
APP_PORT=8000
|
||||||
|
|
||||||
|
# Public endpoint metadata (for ops/documentation)
|
||||||
|
APP_PUBLIC_IP=203.0.113.10
|
||||||
|
APP_PUBLIC_DOMAIN=bot.example.com
|
||||||
|
APP_BASE_URL=https://bot.example.com
|
||||||
|
|
||||||
|
DB_PATH=/opt/max-support/data/app.db
|
||||||
|
SESSION_SECRET=CHANGE_ME_LONG_RANDOM_STRING
|
||||||
|
WEB_ADMIN_LOGIN=admin
|
||||||
|
WEB_ADMIN_PASSWORD=CHANGE_ME_STRONG_PASSWORD
|
||||||
|
|
||||||
|
MAX_BOT_TOKEN=YOUR_MAX_BOT_TOKEN
|
||||||
|
MAX_BOT_API_URL=https://botapi.max.ru
|
||||||
|
|
||||||
|
# Recommended first start: polling mode
|
||||||
|
MAX_USE_WEBHOOK=false
|
||||||
|
MAX_WEBHOOK_URL=
|
||||||
|
MAX_WEBHOOK_SECRET=
|
||||||
|
|
||||||
|
CONTEXT_BACKEND=memory
|
||||||
|
REDIS_URL=redis://127.0.0.1:6379/0
|
||||||
|
|
||||||
|
CHANNEL_LOCAL_ENABLED=true
|
||||||
|
CHANNEL_EXTERNAL_ENABLED=true
|
||||||
|
EXTERNAL_API_CREATE_URL_WITH_KEY=https://external.example/api/create?key=YOUR_KEY
|
||||||
|
|
||||||
|
OPERATOR_CHAT_IDS=123456,987654
|
||||||
|
REQUEST_CATEGORIES=Общая консультация,Техническая проблема,Оплата,Другое
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4) Run one-shot setup on server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/max-support
|
||||||
|
chmod +x deploy/scripts/setup_variant_a.sh
|
||||||
|
APP_DIR=/opt/max-support APP_USER=$USER SERVICE_NAME=max-support ./deploy/scripts/setup_variant_a.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
What this does:
|
||||||
|
1. Creates/updates `.venv`
|
||||||
|
2. Installs dependencies
|
||||||
|
3. Installs `systemd` unit
|
||||||
|
4. Installs `sudoers` rule for restart
|
||||||
|
5. Enables and starts `max-support`
|
||||||
|
6. Runs healthcheck on `127.0.0.1:${APP_PORT}`
|
||||||
|
|
||||||
|
## 5) Woodpecker secrets (required)
|
||||||
|
|
||||||
|
Create these repository secrets:
|
||||||
|
|
||||||
|
1. `deploy_host` - server host (IP or domain), e.g. `203.0.113.10`
|
||||||
|
2. `deploy_user` - SSH user on server, e.g. `deploy`
|
||||||
|
3. `deploy_port` - SSH port, usually `22`
|
||||||
|
4. `deploy_ssh_key` - private SSH key (PEM, multiline)
|
||||||
|
|
||||||
|
No registry secrets are needed in Variant A.
|
||||||
|
|
||||||
|
## 6) SSH and sudo requirements
|
||||||
|
|
||||||
|
`deploy_user` must be able to:
|
||||||
|
1. `cd /opt/max-support && git pull --ff-only`
|
||||||
|
2. `sudo systemctl restart max-support`
|
||||||
|
|
||||||
|
The setup script already installs sudoers rule in `/etc/sudoers.d/max-support-restart`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<deploy_user> ALL=(ALL) NOPASSWD:/bin/systemctl restart max-support,/bin/systemctl status max-support
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7) CI/CD flow now
|
||||||
|
|
||||||
|
On each push to `main/master`:
|
||||||
|
1. Tests run
|
||||||
|
2. CI connects via SSH
|
||||||
|
3. Executes `git pull --ff-only`
|
||||||
|
4. Updates dependencies in `.venv`
|
||||||
|
5. Restarts `max-support` service
|
||||||
|
6. Checks `/health` using `APP_PORT` from `/opt/max-support/.env`
|
||||||
|
|
||||||
|
## 8) Useful commands on server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status max-support
|
||||||
|
sudo journalctl -u max-support -n 200 --no-pager
|
||||||
|
sudo systemctl restart max-support
|
||||||
|
curl -fsS http://127.0.0.1:8000/health
|
||||||
|
```
|
||||||
17
app/run.py
Normal file
17
app/run.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
from app.config import get_settings
|
||||||
|
from app.main import create_app
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
settings = get_settings()
|
||||||
|
app = create_app(settings)
|
||||||
|
uvicorn.run(app, host=settings.app_host, port=settings.app_port)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
72
deploy/scripts/setup_variant_a.sh
Normal file
72
deploy/scripts/setup_variant_a.sh
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# One-shot setup script for Variant A (no Docker runtime).
|
||||||
|
# It prepares venv, installs dependencies, installs systemd unit,
|
||||||
|
# installs sudoers rule for CI restart, and starts the service.
|
||||||
|
|
||||||
|
APP_DIR="${APP_DIR:-/opt/max-support}"
|
||||||
|
APP_USER="${APP_USER:-$USER}"
|
||||||
|
SERVICE_NAME="${SERVICE_NAME:-max-support}"
|
||||||
|
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
|
||||||
|
SYSTEMD_TEMPLATE="${PROJECT_ROOT}/deploy/systemd/max-support.service.template"
|
||||||
|
SUDOERS_TEMPLATE="${PROJECT_ROOT}/deploy/sudoers/max-support-restart.template"
|
||||||
|
|
||||||
|
if [[ ! -d "${APP_DIR}" ]]; then
|
||||||
|
echo "ERROR: APP_DIR '${APP_DIR}' does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "${APP_DIR}/requirements.txt" ]]; then
|
||||||
|
echo "ERROR: '${APP_DIR}/requirements.txt' not found. Clone repo first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "${APP_DIR}/.env" ]]; then
|
||||||
|
echo "ERROR: '${APP_DIR}/.env' not found. Create .env first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Ensuring ownership for ${APP_DIR}"
|
||||||
|
sudo chown -R "${APP_USER}:${APP_USER}" "${APP_DIR}"
|
||||||
|
|
||||||
|
echo "==> Creating/updating venv"
|
||||||
|
if [[ ! -d "${APP_DIR}/.venv" ]]; then
|
||||||
|
sudo -u "${APP_USER}" "${PYTHON_BIN}" -m venv "${APP_DIR}/.venv"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Installing Python dependencies"
|
||||||
|
sudo -u "${APP_USER}" bash -lc "cd '${APP_DIR}' && . .venv/bin/activate && pip install --no-cache-dir -r requirements.txt"
|
||||||
|
|
||||||
|
echo "==> Installing systemd unit"
|
||||||
|
SERVICE_RENDERED="$(sed "s|__APP_USER__|${APP_USER}|g; s|__APP_DIR__|${APP_DIR}|g" "${SYSTEMD_TEMPLATE}")"
|
||||||
|
echo "${SERVICE_RENDERED}" | sudo tee "/etc/systemd/system/${SERVICE_NAME}.service" > /dev/null
|
||||||
|
|
||||||
|
echo "==> Installing sudoers rule for CI restart"
|
||||||
|
SUDOERS_RENDERED="$(sed "s|__APP_USER__|${APP_USER}|g" "${SUDOERS_TEMPLATE}")"
|
||||||
|
echo "${SUDOERS_RENDERED}" | sudo tee "/etc/sudoers.d/${SERVICE_NAME}-restart" > /dev/null
|
||||||
|
sudo chmod 0440 "/etc/sudoers.d/${SERVICE_NAME}-restart"
|
||||||
|
sudo visudo -cf "/etc/sudoers.d/${SERVICE_NAME}-restart"
|
||||||
|
|
||||||
|
echo "==> Reloading and starting service"
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable "${SERVICE_NAME}"
|
||||||
|
sudo systemctl restart "${SERVICE_NAME}"
|
||||||
|
sudo systemctl --no-pager status "${SERVICE_NAME}" || true
|
||||||
|
|
||||||
|
echo "==> Health check"
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "${APP_DIR}/.env"
|
||||||
|
set +a
|
||||||
|
curl -fsS "http://127.0.0.1:${APP_PORT}/health" || {
|
||||||
|
echo "ERROR: health check failed."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Setup completed successfully."
|
||||||
|
|
||||||
2
deploy/sudoers/max-support-restart.template
Normal file
2
deploy/sudoers/max-support-restart.template
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
__APP_USER__ ALL=(ALL) NOPASSWD:/bin/systemctl restart max-support,/bin/systemctl status max-support
|
||||||
|
|
||||||
18
deploy/systemd/max-support.service.template
Normal file
18
deploy/systemd/max-support.service.template
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
[Unit]
|
||||||
|
Description=MAX Support Bot (FastAPI + MAX bot runtime)
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=__APP_USER__
|
||||||
|
WorkingDirectory=__APP_DIR__
|
||||||
|
EnvironmentFile=__APP_DIR__/.env
|
||||||
|
ExecStart=__APP_DIR__/.venv/bin/python -m app.run
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
KillSignal=SIGINT
|
||||||
|
TimeoutStopSec=30
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue