Initial commit: VoIdeaAI - voice-first AI idea assistant
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_URL="https://github.com/anomalyco/voidea.git"
|
||||
INSTALL_DIR="/opt/voidea"
|
||||
BRANCH="main"
|
||||
|
||||
echo "=== VoIdeaAI Deploy ==="
|
||||
|
||||
# Create user if not exists
|
||||
if ! id -u voidea &>/dev/null; then
|
||||
sudo useradd -r -s /bin/bash -m -d "$INSTALL_DIR" voidea
|
||||
fi
|
||||
|
||||
# Install system dependencies
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git python3 python3-venv python3-pip postgresql postgresql-client nginx
|
||||
|
||||
# Clone / pull
|
||||
if [ -d "$INSTALL_DIR/.git" ]; then
|
||||
cd "$INSTALL_DIR"
|
||||
sudo -u voidea git pull origin "$BRANCH"
|
||||
else
|
||||
sudo -u voidea git clone -b "$BRANCH" "$REPO_URL" "$INSTALL_DIR"
|
||||
fi
|
||||
|
||||
# ── Auto version bump ──
|
||||
LAST_COMMIT_FILE="$INSTALL_DIR/.last_deploy_commit"
|
||||
VERSION_FILE="$INSTALL_DIR/VERSION"
|
||||
AGENT_VERSIONS_FILE="$INSTALL_DIR/AGENT_VERSIONS.json"
|
||||
|
||||
if [ -f "$LAST_COMMIT_FILE" ]; then
|
||||
LAST_COMMIT=$(cat "$LAST_COMMIT_FILE")
|
||||
CURRENT_COMMIT=$(git rev-parse HEAD)
|
||||
|
||||
if [ "$LAST_COMMIT" != "$CURRENT_COMMIT" ]; then
|
||||
# Get changed files since last deploy
|
||||
CHANGED_FILES=$(git diff --name-only "$LAST_COMMIT" HEAD)
|
||||
|
||||
# ── Project version bump ──
|
||||
HAS_PROJECT_CHANGES=false
|
||||
while IFS= read -r file; do
|
||||
case "$file" in
|
||||
app/agents/*) ;;
|
||||
AGENT_VERSIONS.json) ;;
|
||||
*) HAS_PROJECT_CHANGES=true ;;
|
||||
esac
|
||||
done <<< "$CHANGED_FILES"
|
||||
|
||||
if [ "$HAS_PROJECT_CHANGES" = true ]; then
|
||||
CURRENT_VER=$(cat "$VERSION_FILE")
|
||||
MAJOR=$(echo "$CURRENT_VER" | cut -d. -f1)
|
||||
MINOR=$(echo "$CURRENT_VER" | cut -d. -f2)
|
||||
PATCH=$(echo "$CURRENT_VER" | cut -d. -f3)
|
||||
|
||||
# Check commit messages for feat/fix hints
|
||||
COMMIT_MSGS=$(git log "$LAST_COMMIT..HEAD" --format=%s)
|
||||
if echo "$COMMIT_MSGS" | grep -q "^feat:"; then
|
||||
MINOR=$((MINOR + 1))
|
||||
PATCH=0
|
||||
else
|
||||
PATCH=$((PATCH + 1))
|
||||
fi
|
||||
|
||||
NEW_VER="${MAJOR}.${MINOR}.${PATCH}"
|
||||
echo "$NEW_VER" > "$VERSION_FILE"
|
||||
echo ">>> Project version: $CURRENT_VER → $NEW_VER"
|
||||
fi
|
||||
|
||||
# ── Agent version bump ──
|
||||
# Mapping: agent file → agent name in AGENT_VERSIONS.json
|
||||
AGENT_FILE_MAP() {
|
||||
echo "$1" | sed -n 's|^app/agents/\(.*\)\.py$|\1|p'
|
||||
}
|
||||
|
||||
while IFS= read -r file; do
|
||||
AGENT_NAME=""
|
||||
case "$file" in
|
||||
app/agents/role_agents.py)
|
||||
AGENT_NAME="role_agents"
|
||||
;;
|
||||
app/agents/conductor_agent.py)
|
||||
AGENT_NAME="conductor"
|
||||
;;
|
||||
app/agents/*.py)
|
||||
BASE=$(basename "$file" .py)
|
||||
# Map filename to agent name (e.g. qa_tester_agent.py → qa_tester_agent)
|
||||
if grep -q "\"$BASE\"" "$AGENT_VERSIONS_FILE" 2>/dev/null; then
|
||||
AGENT_NAME="$BASE"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "$AGENT_NAME" ] && [ -f "$AGENT_VERSIONS_FILE" ]; then
|
||||
CURRENT_AGENT_VER=$(python3 -c "import json; d=json.load(open('$AGENT_VERSIONS_FILE')); print(d.get('$AGENT_NAME','1.0.0'))")
|
||||
MAJOR=$(echo "$CURRENT_AGENT_VER" | cut -d. -f1)
|
||||
MINOR=$(echo "$CURRENT_AGENT_VER" | cut -d. -f2)
|
||||
PATCH=$(echo "$CURRENT_AGENT_VER" | cut -d. -f3)
|
||||
|
||||
# Check if commit has agent-feat for this agent
|
||||
if git log "$LAST_COMMIT..HEAD" --format=%s | grep -iq "agent-feat:.*$AGENT_NAME"; then
|
||||
MINOR=$((MINOR + 1))
|
||||
PATCH=0
|
||||
else
|
||||
PATCH=$((PATCH + 1))
|
||||
fi
|
||||
|
||||
NEW_AGENT_VER="${MAJOR}.${MINOR}.${PATCH}"
|
||||
python3 -c "
|
||||
import json
|
||||
d = json.load(open('$AGENT_VERSIONS_FILE'))
|
||||
d['$AGENT_NAME'] = '$NEW_AGENT_VER'
|
||||
with open('$AGENT_VERSIONS_FILE', 'w', encoding='utf-8') as f:
|
||||
json.dump(d, f, ensure_ascii=False, indent=2)
|
||||
"
|
||||
echo ">>> Agent $AGENT_NAME: $CURRENT_AGENT_VER → $NEW_AGENT_VER"
|
||||
fi
|
||||
done <<< "$CHANGED_FILES"
|
||||
|
||||
# Commit version bumps
|
||||
if [ "$HAS_PROJECT_CHANGES" = true ] || grep -q "^app/agents/" <<< "$CHANGED_FILES"; then
|
||||
git add "$VERSION_FILE" "$AGENT_VERSIONS_FILE"
|
||||
git commit -m "chore: auto-bump versions [skip ci]"
|
||||
git push origin "$BRANCH" || echo "Warning: push failed (might be already pushed)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Save current commit for next deploy
|
||||
git rev-parse HEAD > "$LAST_COMMIT_FILE"
|
||||
|
||||
# Python venv
|
||||
if [ ! -d "$INSTALL_DIR/venv" ]; then
|
||||
sudo -u voidea python3 -m venv "$INSTALL_DIR/venv"
|
||||
fi
|
||||
sudo -u voidea "$INSTALL_DIR/venv/bin/pip" install --upgrade pip
|
||||
sudo -u voidea "$INSTALL_DIR/venv/bin/pip" install -e "$INSTALL_DIR"
|
||||
|
||||
# Install Playwright for QATesterAgent
|
||||
sudo -u voidea "$INSTALL_DIR/venv/bin/pip" install playwright
|
||||
sudo -u voidea "$INSTALL_DIR/venv/bin/playwright" install chromium
|
||||
|
||||
# .env
|
||||
if [ ! -f "$INSTALL_DIR/.env" ]; then
|
||||
sudo -u voidea cp "$INSTALL_DIR/.env.example" "$INSTALL_DIR/.env"
|
||||
echo ">>> Заполните $INSTALL_DIR/.env и запустите deploy.sh снова"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build frontend
|
||||
cd "$INSTALL_DIR/webui"
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
# Run migrations
|
||||
sudo -u voidea "$INSTALL_DIR/venv/bin/alembic" -c "$INSTALL_DIR/alembic.ini" upgrade head
|
||||
|
||||
# Install nginx config
|
||||
if [ -d /etc/nginx/sites-available ]; then
|
||||
sudo cp "$INSTALL_DIR/deploy/voidea.nginx.conf" /etc/nginx/sites-available/voidea
|
||||
if [ ! -f /etc/nginx/sites-enabled/voidea ]; then
|
||||
sudo ln -sf /etc/nginx/sites-available/voidea /etc/nginx/sites-enabled/
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install systemd units
|
||||
sudo cp "$INSTALL_DIR/deploy/voidea-api.service" /etc/systemd/system/
|
||||
sudo cp "$INSTALL_DIR/deploy/voidea-worker.service" /etc/systemd/system/
|
||||
sudo cp "$INSTALL_DIR/deploy/voidea-beat.service" /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# Write deploy timestamp for debug mode auto-enable (48h)
|
||||
date +%s | sudo -u voidea tee "$INSTALL_DIR/.last_deploy" > /dev/null
|
||||
|
||||
# Enable and start
|
||||
sudo systemctl enable voidea-api
|
||||
sudo systemctl enable voidea-worker
|
||||
sudo systemctl enable voidea-beat
|
||||
sudo systemctl restart voidea-api
|
||||
sudo systemctl restart voidea-worker
|
||||
sudo systemctl restart voidea-beat
|
||||
|
||||
# Restart nginx if config exists
|
||||
if [ -f /etc/nginx/sites-enabled/voidea ]; then
|
||||
sudo systemctl reload nginx || true
|
||||
fi
|
||||
|
||||
echo "=== Deploy complete ==="
|
||||
echo "Check status: sudo systemctl status voidea-api"
|
||||
@@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=VoIdeaAI API Service
|
||||
After=network.target postgresql.service
|
||||
Wants=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=voidea
|
||||
Group=voidea
|
||||
WorkingDirectory=/opt/voidea
|
||||
EnvironmentFile=/opt/voidea/.env
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
LimitNOFILE=65536
|
||||
ExecStart=/opt/voidea/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8020 --workers 2
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=VoIdeaAI Beat Scheduler
|
||||
After=network.target voidea-api.service
|
||||
Wants=voidea-api.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=voidea
|
||||
Group=voidea
|
||||
WorkingDirectory=/opt/voidea
|
||||
EnvironmentFile=/opt/voidea/.env
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
LimitNOFILE=65536
|
||||
ExecStart=/opt/voidea/venv/bin/python -m app.beat
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=VoIdeaAI Background Worker
|
||||
After=network.target voidea-api.service
|
||||
Wants=voidea-api.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=voidea
|
||||
Group=voidea
|
||||
WorkingDirectory=/opt/voidea
|
||||
EnvironmentFile=/opt/voidea/.env
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
LimitNOFILE=65536
|
||||
ExecStart=/opt/voidea/venv/bin/python -m app.worker
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,89 @@
|
||||
# VoIdeaAI — Nginx configuration
|
||||
# Place in /etc/nginx/sites-available/voidea
|
||||
# Enable: sudo ln -sf /etc/nginx/sites-available/voidea /etc/nginx/sites-enabled/
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name voidea.ru www.voidea.ru;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name voidea.ru www.voidea.ru;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/voidea.ru/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/voidea.ru/privkey.pem;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=63072000" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
# Static assets (long cache)
|
||||
location /assets/ {
|
||||
proxy_pass http://127.0.0.1:8020;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_cache_valid 200 365d;
|
||||
expires 365d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location /icons/ {
|
||||
proxy_pass http://127.0.0.1:8020;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_cache_valid 200 365d;
|
||||
expires 365d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# API proxy
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8020;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
}
|
||||
|
||||
# SSE support
|
||||
location /api/v1/voice/stream/ {
|
||||
proxy_pass http://127.0.0.1:8020;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection '';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
chunked_transfer_encoding on;
|
||||
}
|
||||
|
||||
# WebSocket support (for future use)
|
||||
location /ws/ {
|
||||
proxy_pass http://127.0.0.1:8020;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# Static files + SPA fallback
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8020;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_buffering off;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user