Initial commit: VoIdeaAI - voice-first AI idea assistant

This commit is contained in:
2026-05-13 12:51:42 +03:00
commit 688d043dad
421 changed files with 47915 additions and 0 deletions
+189
View File
@@ -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"
+22
View File
@@ -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
+21
View File
@@ -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
+21
View File
@@ -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
+89
View File
@@ -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;
}
}