This documentation covers the deployment of a (mostly) GDPR-compliant Multi-Provider AI Suite with:
- π¬ Chat (Multiple LLM providers with context-aware history, tool calling, web search)
- π Web Search (Agentic tool-calling loop via Tavily, Brave Search, DuckDuckGo, or self-hosted SearXNG)
- ποΈ Audio Transcription (Gladia, Whisper, Mistral, Deepgram, AssemblyAI; Speaker Diarization; Chunking; real-time Dictation)
- π Content Extraction for uploads (text extraction from PPTX, DOCX, XLSX, PDF; OCR for images/scans) and OCR (with Mistral OCR, local Tesseract, Vision LLMs etc)
- π€ Translation (NMT + LLM-based DOCX translation via
translator.py) - π Format-Transplant (Apply formatting/styles of one DOCX to the content of another)
- π¦ Cloud Storage (Hetzner Storage Box integration via SMB)
- π Resumable Jobs (Job tracking for large files)
- ποΈ Vision Analysis (Image understanding)
- π¨ Image Generation (e.g. FLUX models via Nebius and directly per Black Forest Labs API)
- πΎ Database Integration (SQLite with per-user AES-256-GCM encryption)
- π± PWA Support (Progressive Web App for mobile installation)
- π‘οΈ Security (Fail2Ban protection, EU-only mode, per-user key wrapping)
The application runs as a Python/Gradio service on localhost:7860, managed by systemd, and exposed via Apache reverse proxy with SSL.
The suite supports multiple LLM providers. EU_ONLY_MODE = True in config.py restricts which providers regular users can access. Admins and Medienverwalter bypass this restriction.
| Provider | EU Status | Notes |
|---|---|---|
| Mistral | π«π· EU (Paris) | Chat, Vision, OCR, Audio |
| Scaleway | π«π· EU (Paris) | Chat, Vision, Transcription |
| Nebius | π³π± EU (Amsterdam) | Chat, Vision, Image Generation |
| Gladia | π«π· EU | Transcription |
| Deepgram | πͺπΊ EU endpoint | Transcription (api.eu.deepgram.com) |
| AssemblyAI | πͺπΊ EU endpoint | Transcription (api.eu.assemblyai.com) |
| BFL | π©πͺ EU (Germany) | Image Generation (FLUX) |
| Requesty | πͺπΊ EU router | Chat β EU-filtered (see below) |
| Langdock | π©πͺ EU (Hamburg, Azure) | Chat β DSGVO-konform, OpenAI-compatible |
| OpenRouter | πΊπΈ US | Chat, Vision, Image β restricted in EU mode |
| Groq | πΊπΈ US | Chat, Transcription β restricted in EU mode |
| Poe | πΊπΈ US | Chat, Vision β restricted in EU mode |
Requesty (router.eu.requesty.ai) is a model aggregator routing to 20+ backend providers. The suite applies client-side EU filtering based on model ID prefix and @region suffix:
- EU-native (always allowed):
nebius/β¦,mistral/β¦ - EU-regional (allowed only with explicit EU region in model ID):
azure/model@swedencentral,azure/model@francecentral,azure/model@uksouthbedrock/model@eu-central-1,bedrock/model@eu-west-1, etc.vertex/model@europe-west1,vertex/model@europe-central2, etc.
- Blocked:
openai/β¦,anthropic/β¦,novita/β¦,groq/β¦,xai/β¦,moonshot/β¦,deepseek/β¦,alibaba/β¦, and others
Role-based access: Admins and Medienverwalter see all 430+ Requesty models (incl. non-EU). Regular users see only the ~117 EU-routed models.
Langdock is a German company (Hamburg) providing an OpenAI-compatible API backed by EU Azure infrastructure, fully DSGVO-compliant.
- All models served by Langdock are chat models (no image-gen, no embeddings)
- No additional EU filtering required β all models are on EU Azure by definition
- Use Fetch Models to always get the live list from Langdock's API
The chat supports an agentic tool-calling loop via tool_executor.py. When enabled, the model can call tools (currently: web_search) mid-conversation and use the results to ground its final answer.
Supported providers for tool calling: Mistral, Scaleway, Nebius, OpenRouter
Search backends (selectable in the βοΈ Konfiguration tab):
| Backend | Key Required | Notes |
|---|---|---|
tavily |
TAVILY_API_KEY |
Default. AI-optimised results, free 1 000 req/month |
brave_web |
BRAVE_SEARCH_API_KEY |
Web snippets, own index, EU-friendly, ~1 000 free/month via $5 credit |
brave_llm |
BRAVE_SEARCH_API_KEY |
Pre-extracted text chunks optimised for LLM grounding / RAG |
brave_answers |
BRAVE_ANSWERS_API_KEY |
AI-generated answer with citations (separate Brave subscription) |
brave_images |
BRAVE_SEARCH_API_KEY |
Image search |
duckduckgo |
β | No key needed, rate-limited, production fallback |
searxng |
SEARXNG_URL |
Self-hosted meta-search (future option) |
Fallback chain: if the primary backend fails, the router automatically tries duckduckgo before surfacing an error.
Options exposed per search call: freshness (24h/7d/31d/365d), country, search_lang, safesearch, extra_snippets, max_tokens (LLM context), enable_citations (Answers).
The tool_executor.py module is standalone-testable without running the full app:
python tool_executor.py --query "What is the latest news about Mistral AI?" --backend tavily
python tool_executor.py --query "..." --backend brave_web --provider Scaleway --model mistral-small-3.2-24b-instruct-2506- OS: Ubuntu 20.04 LTS or newer
- RAM: Minimum 8GB (for handling audio files)
- Root/Sudo Access
- Domain: DNS A-Record pointing to server IP (e.g.,
ai.yourdomain.de) - Storage: Hetzner Storage Box with sub-account access
- API Keys (stored in
.envfile):MISTRAL_API_KEYβ multipurpose: chat, OCR, vision, audioSCALEWAY_API_KEYβ chat, transcriptionNEBIUS_API_KEYβ chat, image generationGLADIA_API_KEYβ long-form transcriptionDEEPGRAM_API_KEYβ transcription, EU endpointASSEMBLYAI_API_KEYβ transcription, EU endpointGROQ_API_KEYβ transcription, chat (US, restricted in EU mode)REQUESTY_API_KEYβ EU router: chat across many providersLANGDOCK_API_KEYβ chat, DSGVO-konform, German company, EU AzureOPENROUTER_API_KEYβ optional, additional models (US, restricted in EU mode)BFL_API_KEYβ optional, FLUX image generationPOE_API_KEYβ optional, additional models (US, restricted in EU mode)TAVILY_API_KEYβ web search, free 1 000 req/monthBRAVE_SEARCH_API_KEYβ web search: snippets, LLM context, imagesBRAVE_ANSWERS_API_KEYβ web search: AI-generated answers (separate subscription)SEARXNG_URLβ optional, self-hosted SearXNG base URL (e.g.http://localhost:8888)
CRITICAL: FFmpeg must be in PATH. cifs-utils is required for Storage Box. OCR and document tools are required for the Content Extractor.
sudo apt update
sudo apt upgrade -y
# Install all required system packages
sudo apt install -y \
wget \
python3-pip \
ffmpeg \
apache2 \
certbot \
python3-certbot-apache \
sqlite3 \
fail2ban \
cifs-utils \
tesseract-ocr \
tesseract-ocr-deu \
poppler-utils \
pandocCRITICAL: You must allow SSH before enabling the firewall, or you will lock yourself out.
# 1. Allow incoming SSH connections
sudo ufw allow OpenSSH
# 2. Allow Web Traffic (HTTP/HTTPS)
sudo ufw allow 'Apache Full'
# 3. Enable the Firewall
sudo ufw enable
# 4. Verify Status
sudo ufw statussudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstabVerify critical installations:
which ffmpeg # Must show: /usr/bin/ffmpeg
sudo systemctl status fail2ban # Must be: active (running)sudo mkdir -p /mnt/storagesudo nano /etc/cifs-credentialsContent:
username=u12345-sub1
password=YOUR_SUBACCOUNT_PASSWORDsudo chmod 600 /etc/cifs-credentialssudo nano /etc/fstabAdd (single line):
//u12345-sub1.your-storagebox.de/u12345-sub1 /mnt/storage cifs credentials=/etc/cifs-credentials,uid=0,gid=0,file_mode=0770,dir_mode=0770,nounix,vers=3.0,x-systemd.automount,x-systemd.idle-timeout=60 0 0
Note:
uid=0assumes app runs as root. Change to1000for non-root user.
sudo systemctl daemon-reload
sudo systemctl restart remote-fs.target
ls -la /mnt/storage # Should list files from Storage Boxsudo mkdir -p /var/www/transkript_app
sudo mkdir -p /var/www/transkript_app/static
sudo mkdir -p /var/www/transkript_app/generated_images
sudo mkdir -p /var/www/transkript_app/jobs
cd /var/www/transkript_appOption A: Clone from GitHub
git clone https://github.com/YOUR_USERNAME/YOUR_REPO.git .Option B: Manual File Upload
Upload these files:
app.pyβ Main application (UI, event wiring, Gradio blocks)config.pyβ Central provider & key configurationcrypto_utils.pyβ Encryption logic & key wrappingdb_models.pyβ SQLAlchemy models & schema migrationsdb_ops.pyβ Database CRUD operationsprovider_utils.pyβ LLM provider clients, compliance, routingcontext_utils.pyβ Token estimation, context pruning, chunkingimage_utils.pyβ Image encoding & resize helpersimage_gen_utils.pyβ Image generation & vision analysistranscription_utils.pyβ Audio processing, transcription runners, YouTube/download helpersstorage_utils.pyβ File storage, pCloud, per-user directorieschat_handlers.pyβ Chat logic, content extraction (UniversalExtractor), attachmentsocr_utils.pyβ OCR engines (Mistral, Groq, OpenRouter, Ollama/GLM, Vision LLM)translation_utils.pyβ Async DOCX translation with progress streamingexport_utils.pyβ Export to .docx / .md / .txttool_executor.pyβ Web search / tool-calling routertranslator.pyβ DOCX translation engine (NMT + LLM)format_transplant.pyβ DOCX format-transplant enginedictation_manager.pyβ Real-time dictation (Gladia, Mistral)requirements.txtβ Python dependenciesstatic/folder:custom.css,manifest.json,pwa.js,service-worker.js,icon-192.png,icon-512.png
CRITICAL: Store all API keys in a secured .env file.
sudo nano /var/www/transkript_app/.envRequired content:
# LLM Providers
MISTRAL_API_KEY=...
SCALEWAY_API_KEY=...
NEBIUS_API_KEY=...
OPENROUTER_API_KEY=...
REQUESTY_API_KEY=rqsty-sk-...
LANGDOCK_API_KEY=sk-...
# Transcription
GLADIA_API_KEY=...
DEEPGRAM_API_KEY=...
ASSEMBLYAI_API_KEY=...
GROQ_API_KEY=gsk_...
# Image Generation
BFL_API_KEY=...
# Web Search (tool calling)
TAVILY_API_KEY=tvly-... # Free 1 000 req/month β https://tavily.com
BRAVE_SEARCH_API_KEY=BSA... # ~1 000 free/month via $5 credit β https://brave.com/search/api/
BRAVE_ANSWERS_API_KEY=BSA... # Separate Brave Answers subscription
# SEARXNG_URL=http://localhost:8888 # Optional: self-hosted SearXNG
# Optional / US providers (restricted in EU mode)
POE_API_KEY=...
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
# Misc
GRADIO_ANALYTICS_ENABLED=FalseSecure it:
sudo chmod 600 /var/www/transkript_app/.envLocal development: The app automatically loads
.envviapython-dotenvwhen running locally (python app.py). On the VPS the env vars are loaded by systemd'sEnvironmentFile=directive instead.
# 1. Download and Install Miniconda
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh -b -p /opt/miniconda
# 2. Initialize Conda
/opt/miniconda/bin/conda init bash
source ~/.bashrc
# Accept TOS
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r
# 3. Create the 'ak_suite' environment with Python 3.10
/opt/miniconda/bin/conda create -n ak_suite python=3.10 -y
# 4. Link it to the app directory
cd /var/www/transkript_app
rm -rf venv
ln -s /opt/miniconda/envs/ak_suite ./venv
conda activate ak_suiteconda activate ak_suite
pip install --upgrade pip wheel
pip install \
"openai>=1.0" \
"gradio>=5.0" \
"yt-dlp>=2024.11" \
requests \
pillow \
pydub \
sqlalchemy \
bcrypt \
fastapi-poe \
python-pptx \
python-docx \
pypdf \
pymupdf \
pdf2image \
pytesseract \
pandas \
openpyxl \
lxml \
pycryptodome \
cryptography \
python-dotenv \
ddgsOptional but recommended:
pip install pqcrypto # Post-quantum encryption (Kyber-512)We use pycryptodome for AES-GCM and cryptography for Fernet/PBKDF2. python-dotenv enables .env loading locally. ddgs provides DuckDuckGo search as a free fallback (no API key).
Alternatively, for single installs without activating the environment:
/var/www/transkript_app/venv/bin/pip install [package]
sudo touch /var/www/transkript_app/app.log
sudo chmod 666 /var/www/transkript_app/app.log
sudo chmod -R 755 /var/www/transkript_app
sudo chown -R www-data:www-data /var/www/transkript_app/static
sudo chmod -R 755 /var/www/transkript_app/staticThis configuration serves PWA files directly via Apache and proxies the Gradio app with proper HTTPS headers.
sudo a2enmod proxy proxy_http proxy_wstunnel rewrite headers sslsudo certbot --apache -d ai.yourdomain.deEdit /etc/apache2/sites-available/transkript.conf:
<VirtualHost *:80>
ServerName ai.yourdomain.de
ErrorLog ${APACHE_LOG_DIR}/transkript_error.log
CustomLog ${APACHE_LOG_DIR}/transkript_access.log combined
RewriteEngine On
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>Edit /etc/apache2/sites-available/transkript-le-ssl.conf:
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName ai.yourdomain.de
# =================================================
# π‘οΈ SECURITY HEADERS
# =================================================
Header always unset Server
Header always unset X-Powered-By
Header unset Server
Header unset X-Powered-By
# =================================================
# π BLOCK OPENAPI - FIRST PRIORITY
# =================================================
RewriteEngine On
RewriteRule ^/openapi\.json$ - [F,L]
RewriteRule ^/docs/?$ - [F,L]
RewriteRule ^/redoc/?$ - [F,L]
RewriteRule ^/api(/.*)?$ - [F,L]
RewriteRule ^/gradio_api/openapi\.json$ - [F,L]
# =================================================
# 1. STATIC FILES (Served by Apache)
# =================================================
Alias /static /var/www/transkript_app/static
<Directory /var/www/transkript_app/static>
Require all granted
Options -Indexes
AddType text/css .css
AddType application/javascript .js
AddType image/png .png
Header set Cache-Control "public, max-age=31536000, immutable"
</Directory>
Alias /manifest.json /var/www/transkript_app/static/manifest.json
<Files "manifest.json">
Require all granted
Header set Content-Type "application/manifest+json"
Header set Cache-Control "no-cache"
</Files>
Alias /service-worker.js /var/www/transkript_app/static/service-worker.js
<Files "service-worker.js">
Require all granted
Header set Content-Type "application/javascript"
Header set Cache-Control "no-cache"
Header set Service-Worker-Allowed "/"
</Files>
# =================================================
# 2. PROXY SETTINGS (Gradio)
# =================================================
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
RequestHeader set X-Forwarded-Host "ai.yourdomain.de"
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://127.0.0.1:7860/$1 [P,L]
ProxyPass /static !
ProxyPass /manifest.json !
ProxyPass /service-worker.js !
ProxyPass / http://127.0.0.1:7860/
ProxyPassReverse / http://127.0.0.1:7860/
# =================================================
# 3. SSL CONFIGURATION
# =================================================
SSLCertificateFile /etc/letsencrypt/live/ai.yourdomain.de/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/ai.yourdomain.de/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
LimitRequestBody 1048576000
ProxyTimeout 600
TimeOut 600
</VirtualHost>
</IfModule>sudo a2ensite transkript.conf
sudo a2ensite transkript-le-ssl.conf
sudo apache2ctl configtest # Should show "Syntax OK"
sudo systemctl restart apache2Create /etc/systemd/system/transkript.service:
[Unit]
Description=Gradio App
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/var/www/transkript_app
Environment="PATH=/var/www/transkript_app/venv/bin:/usr/local/bin:/usr/bin:/bin"
Environment="GRADIO_ANALYTICS_ENABLED=False"
Environment="GRADIO_SERVER_NAME=127.0.0.1"
EnvironmentFile=/var/www/transkript_app/.env
ExecStart=/var/www/transkript_app/venv/bin/python app.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetKey Points:
EnvironmentFile=/var/www/transkript_app/.envβ loads API keys from .env (replacesload_dotenvfor production)User=rootβ runs as root (change if using non-root deployment)Restart=alwaysβ auto-restart on crashes
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable transkript
sudo systemctl start transkript
sudo systemctl status transkriptsudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local[sshd]
enabled = true
[apache-auth]
enabled = true
[apache-badbots]
enabled = true
[apache-noscript]
enabled = true
[apache-overflows]
enabled = true
[apache-404-scan]
enabled = true
port = http,https
filter = apache-404-scan
logpath = /var/log/apache2/*access.log
maxretry = 5
findtime = 600
bantime = 3600sudo systemctl restart fail2ban
sudo fail2ban-client statussudo nano /etc/logrotate.d/transkript_appContent:
/var/www/transkript_app/app.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 666 root root
copytruncate
}
Test:
sudo logrotate -d /etc/logrotate.d/transkript_appThe app runs locally without any server configuration:
# Clone repo
git clone https://github.com/YOUR_USERNAME/YOUR_REPO.git
cd AIToolkit
# Install dependencies
pip install -r requirements.txt
pip install python-dotenv ddgs
# Create .env with your API keys (see Step 3.3 for key names)
cp .env.example .env # or create manually
# Run
python app.py
# β http://127.0.0.1:7860Local vs VPS differences:
.envis loaded automatically viapython-dotenvlocally; on VPS it's loaded by systemd- Storage mounts to
./storage/locally instead of/mnt/akademie_storage - Encryption keys saved to
./.master_keylocally instead of/var/www/transkript_app/.master_key - Both directories are created automatically if missing
Reset local passwords:
python -c "
import bcrypt, sqlite3
db = sqlite3.connect('akademie_suite.db')
for user, pw in [('admin123', 'admin123'), ('user123', 'user123')]:
h = bcrypt.hashpw(pw.encode(), bcrypt.gensalt(12)).decode()
db.execute('UPDATE users SET password_hash=? WHERE username=?', (h, user))
print(f'Reset {user}')
db.commit(); db.close()
"Test web search standalone:
python tool_executor.py --query "latest Mistral AI news" --backend tavily
python tool_executor.py --query "..." --backend brave_web --provider Scaleway/var/www/transkript_app/
βββ app.py # Main application (UI, Gradio blocks, event wiring)
β
β ββ Core ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ config.py # Provider & key configuration
βββ crypto_utils.py # AES-256-GCM encryption & key wrapping
βββ db_models.py # SQLAlchemy models & schema migrations
βββ db_ops.py # Database CRUD operations
β
β ββ Provider / Inference ββββββββββββββββββββββββββββββββββββββ
βββ provider_utils.py # LLM provider clients, compliance, routing
βββ context_utils.py # Token estimation, context pruning, chunking
βββ image_utils.py # Image encoding & resize helpers
βββ image_gen_utils.py # Image generation & vision analysis
β
β ββ Feature Modules ββββββββββββββββββββββββββββββββββββββββββ
βββ transcription_utils.py # Audio processing, transcription runners, YouTube/download helpers
βββ storage_utils.py # File storage, pCloud, per-user directories
βββ chat_handlers.py # Chat logic, UniversalExtractor, attachments
βββ ocr_utils.py # OCR engines (Mistral, Groq, OpenRouter, Ollama/GLM, Vision LLM)
βββ translation_utils.py # Async DOCX translation with progress streaming
βββ export_utils.py # Export to .docx / .md / .txt
β
β ββ Standalone Tools βββββββββββββββββββββββββββββββββββββββββ
βββ tool_executor.py # Web search / tool-calling router (standalone-testable)
βββ translator.py # DOCX translation engine (NMT + LLM)
βββ format_transplant.py # DOCX format-transplant engine
βββ dictation_manager.py # Real-time dictation (Gladia, Mistral)
βββ fn2md.py # Function-to-markdown helper
β
β ββ Runtime βββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ requirements.txt # Python dependencies
βββ .env # API keys (NOT in repo β create manually)
βββ .master_key # Global encryption key (CRITICAL β backup!)
βββ .pq_keypair # Optional post-quantum keypair
βββ akademie_suite.db # SQLite database (auto-created)
βββ app.log # Application logs
βββ venv/ # Python virtual environment (symlink to conda)
βββ jobs/ # Resume job manifests (auto-created)
βββ generated_images/ # AI-generated images (auto-created)
βββ storage/ # Local fallback when Storage Box unavailable
βββ static/ # PWA assets (owned by www-data)
βββ custom.css
βββ pwa.js
βββ manifest.json
βββ service-worker.js
βββ icon-192.png
βββ icon-512.png
/mnt/akademie_storage/ # Hetzner Storage Box (mounted via CIFS)
βββ users/[username]/ # Per-user storage
β οΈ Critical backup: Always back up.master_keytogether withakademie_suite.db. The database is encrypted β without the key, data is unrecoverable.
sudo systemctl status transkript
sudo journalctl -u transkript -f
tail -f /var/www/transkript_app/app.log
# Common issues:
# - Missing .env β Create /var/www/transkript_app/.env
# - Missing FFmpeg β sudo apt install ffmpeg
# - Port in use β sudo lsof -i :7860The app uses python-dotenv to load .env at startup. Verify:
python -c "from dotenv import load_dotenv; load_dotenv(); import os; print(os.environ.get('MISTRAL_API_KEY','MISSING')[:8])"# Test each backend directly
python tool_executor.py --query "test" --backend tavily
python tool_executor.py --query "test" --backend duckduckgo
# Check keys are loaded
python -c "from config import API_KEYS; print('TAVILY:', bool(API_KEYS.get('TAVILY')))"This happens when your OpenRouter account privacy settings conflict with the selected model. The app automatically adds allow_fallbacks: true to all OpenRouter requests to mitigate this. If it persists, check your OpenRouter privacy settings at https://openrouter.ai/settings/privacy.
curl -I https://ai.yourdomain.de/manifest.json
curl -I https://ai.yourdomain.de/service-worker.js
curl -I https://ai.yourdomain.de/static/icon-192.png
# All should return 200 OKmount | grep storage
sudo mount -t cifs //u12345-sub1.your-storagebox.de/u12345-sub1 /mnt/storage -o credentials=/etc/cifs-credentials,uid=0,gid=0sudo apache2ctl configtest
sudo tail -f /var/log/apache2/error.log
sudo systemctl restart apache2cd /var/www/transkript_app
git pull
source venv/bin/activate
pip install -r requirements.txt --upgrade
sudo systemctl restart transkriptmkdir -p /var/backups/transkript
DATE=$(date +%Y%m%d)
cp /var/www/transkript_app/akademie_suite.db /var/backups/transkript/db-$DATE.bak
cp /var/www/transkript_app/.master_key /var/backups/transkript/master_key-$DATE.bak
[ -f /var/www/transkript_app/.pq_keypair ] && \
cp /var/www/transkript_app/.pq_keypair /var/backups/transkript/pq_keypair-$DATE.baksudo certbot renew --dry-rundf -h /mnt/akademie_storage
df -h /var/www/transkript_appsudo systemctl start transkript
sudo systemctl stop transkript
sudo systemctl restart transkript
sudo systemctl status transkriptsudo journalctl -u transkript -f
tail -f /var/www/transkript_app/app.log
sudo tail -f /var/log/apache2/error.log- Application:
/var/www/transkript_app/app.pyβ UI & event wiring - Chat logic:
/var/www/transkript_app/chat_handlers.py - OCR engines:
/var/www/transkript_app/ocr_utils.py - Transcription:
/var/www/transkript_app/transcription_utils.py - Tool Router:
/var/www/transkript_app/tool_executor.py - Provider Config:
/var/www/transkript_app/config.py - Environment:
/var/www/transkript_app/.envβ secrets - Encryption Key:
/var/www/transkript_app/.master_keyβ backup this! - Service:
/etc/systemd/system/transkript.service - Apache SSL:
/etc/apache2/sites-available/transkript-le-ssl.conf
- β
.envpermissions set to600 - β
.master_keypermissions set to600β backup regularly with the DB - β
/etc/cifs-credentialspermissions set to600 - β Fail2Ban enabled and running
- β SSL certificate valid and auto-renewing
- β
Firewall configured (only ports
22, 80, 443open) - β Apache upload limits configured (1GB)
- β Storage Box mounted with restricted permissions
- β
EU-only mode enabled (
EU_ONLY_MODE = Trueinconfig.py) - β
Requesty configured with EU router endpoint (
router.eu.requesty.ai) - β
OpenRouter
allow_fallbacks: trueset to avoid data-policy 404s - β
Web search keys stored in
.env, never hardcoded
Last Updated: March 2026 β refactored to modular architecture (app.py ~5,300 lines, down from ~8,500)