[{"data":1,"prerenderedAt":2430},["ShallowReactive",2],{"search-de":3,"content-de-vision\u002Fself-hosting":4,"surround-de-\u002Fvision\u002Fself-hosting":2422},[],{"id":5,"title":6,"body":7,"description":2415,"extension":2416,"meta":2417,"navigation":531,"path":2418,"seo":2419,"stem":2420,"__hash__":2421},"content_de\u002F5.vision\u002F1.self-hosting.md","Self-Hosting-Architektur",{"type":8,"value":9,"toc":2397},"minimark",[10,14,18,26,31,34,42,286,314,319,327,371,378,382,388,1178,1204,1208,1235,1239,1383,1395,1424,1434,1438,1451,1558,1562,1679,1683,1691,1768,1785,1789,1792,1796,1799,1865,1901,1905,1908,1952,2072,2076,2079,2171,2210,2222,2226,2375,2393],[11,12,6],"h1",{"id":13},"self-hosting-architektur",[15,16,17],"p",{},"Owlat ist darauf ausgelegt, vollständig auf Ihrer eigenen Infrastruktur zu laufen. Jede Komponente ist quelloffen, und der gesamte Stack wird über eine einzige Docker-Compose-Datei deployt.",[19,20,23],"callout",{"title":21,"type":22},"Warum Self-Hosting zählt","info",[15,24,25],{},"Datensouveränität, Compliance-Anforderungen, abgeschottete Umgebungen oder schlicht der Wunsch nach voller Kontrolle. Owlat benötigt für seinen Betrieb keinerlei Cloud-Dienste — nicht einmal für KI-Funktionen, sofern Sie ein lokales Modell betreiben.",[27,28,30],"h2",{"id":29},"der-stack","Der Stack",[32,33],"arch-self-hosting",{},[15,35,36,37,41],{},"Der selbst gehostete Stack ist in der ",[38,39,40],"code",{},"docker-compose.yml"," im Wurzelverzeichnis des Repositorys definiert:",[43,44,45,61],"table",{},[46,47,48],"thead",{},[49,50,51,55,58],"tr",{},[52,53,54],"th",{},"Dienst",[52,56,57],{},"Rolle",[52,59,60],{},"Image",[62,63,64,81,109,129,147,162,177,198,218,238,264],"tbody",{},[49,65,66,73,76],{},[67,68,69],"td",{},[70,71,72],"strong",{},"Convex-Backend",[67,74,75],{},"Datenbank, Echtzeit-Subscriptions, Dateispeicher, Vektorsuche, serverlose Funktionen",[67,77,78],{},[38,79,80],{},"ghcr.io\u002Fget-convex\u002Fconvex-backend",[49,82,83,97,104],{},[67,84,85,88,89],{},[70,86,87],{},"Convex-Dashboard"," ",[90,91,92,93,96],"em",{},"(",[38,94,95],{},"--profile dashboard",")",[67,98,99,100,103],{},"Admin-\u002FDebugging-Oberfläche für das Backend (Port 6791, nur an ",[38,101,102],{},"127.0.0.1"," gebunden, keine eingebaute Authentifizierung — erreichen Sie sie über einen SSH-Tunnel)",[67,105,106],{},[38,107,108],{},"ghcr.io\u002Fget-convex\u002Fconvex-dashboard",[49,110,111,116,119],{},[67,112,113],{},[70,114,115],{},"Web",[67,117,118],{},"Nuxt-Anwendung (Dashboard, E-Mail-Builder, Einstellungen)",[67,120,121,124,125,128],{},[38,122,123],{},"ghcr.io\u002Fwolvesdotink\u002Fweb"," (oder aus ",[38,126,127],{},"apps\u002Fweb\u002FDockerfile"," gebaut)",[49,130,131,136,139],{},[67,132,133],{},[70,134,135],{},"MTA",[67,137,138],{},"Eigener Mail Transfer Agent — SMTP-Zustellung, Bounce-Verarbeitung, IP-Warming",[67,140,141,124,144,128],{},[38,142,143],{},"ghcr.io\u002Fwolvesdotink\u002Fmta",[38,145,146],{},"apps\u002Fmta\u002FDockerfile",[49,148,149,154,157],{},[67,150,151],{},[70,152,153],{},"Redis",[67,155,156],{},"Job-Queue für den MTA, verteilte Koordination, Zustand des Rate Limitings",[67,158,159],{},[38,160,161],{},"redis:7.4-alpine",[49,163,164,169,172],{},[67,165,166],{},[70,167,168],{},"ClamAV",[67,170,171],{},"Virenprüfung für E-Mail-Anhänge",[67,173,174],{},[38,175,176],{},"clamav\u002Fclamav:stable",[49,178,179,184,190],{},[67,180,181],{},[70,182,183],{},"Updater",[67,185,186,187],{},"Sidecar für In-App-Updates — steuert ",[38,188,189],{},"docker compose pull && up -d",[67,191,192,124,195,128],{},[38,193,194],{},"ghcr.io\u002Fwolvesdotink\u002Fupdater",[38,196,197],{},"apps\u002Fupdater\u002FDockerfile",[49,199,200,210,213],{},[67,201,202,88,205],{},[70,203,204],{},"Caddy",[90,206,92,207,96],{},[38,208,209],{},"--profile tls",[67,211,212],{},"Reverse Proxy mit automatischem HTTPS für die Produktion",[67,214,215],{},[38,216,217],{},"caddy:2.8-alpine",[49,219,220,230,233],{},[67,221,222,88,225],{},[70,223,224],{},"Convex Deploy",[90,226,92,227,96],{},[38,228,229],{},"--profile deploy",[67,231,232],{},"Einmal-Job, der die Tenant-Funktionen ins Backend pusht",[67,234,235],{},[38,236,237],{},"ghcr.io\u002Fwolvesdotink\u002Fconvex-deploy",[49,239,240,254,257],{},[67,241,242,88,245],{},[70,243,244],{},"Code Worker",[90,246,92,247,250,251,96],{},[38,248,249],{},"--profile inbox-codetasks",", auch ",[38,252,253],{},"dev",[67,255,256],{},"Sidecar für den KI-Coding-Agenten der Strecke Feature-Wunsch → Prototyp",[67,258,259,260,263],{},"Aus ",[38,261,262],{},"apps\u002Fcode-worker\u002FDockerfile"," gebaut",[49,265,266,278,281],{},[67,267,268,88,271],{},[70,269,270],{},"Ollama",[90,272,92,273,250,276,96],{},[38,274,275],{},"--profile ai",[38,277,253],{},[67,279,280],{},"Optionale lokale LLM-Inferenz (keine veröffentlichten Ports; nur internes Netzwerk)",[67,282,283],{},[38,284,285],{},"ollama\u002Follama",[15,287,288,289,292,293,295,296,299,300,303,304,307,308,313],{},"Ein lokales LLM kommt als ",[70,290,291],{},"optionaler"," Dienst mit. Starten Sie den mitgelieferten Ollama-Container mit ",[38,294,275],{}," (auch unter ",[38,297,298],{},"--profile dev"," aktiviert) und setzen Sie ",[38,301,302],{},"LLM_PROVIDER=ollama"," (das Backend löst ihn unter ",[38,305,306],{},"http:\u002F\u002Follama:11434\u002Fv1"," auf), oder richten Sie dieselbe Variable auf eine von Ihnen bereitgestellte Ollama-Instanz — siehe ",[309,310,312],"a",{"href":311},"#llm-configuration-for-agent-pipeline","LLM-Konfiguration"," weiter unten.",[315,316,318],"h3",{"id":317},"was-convex-von-haus-aus-mitbringt","Was Convex von Haus aus mitbringt",[15,320,321,322,326],{},"Das quelloffene Convex-Backend (",[309,323,325],{"href":324},"\u002Fdeveloper\u002Fdecisions\u002F006-self-hosted-convex","ADR-006",") bündelt Fähigkeiten, die sonst eigene Dienste erfordern würden:",[328,329,330,337,343,353,359,365],"ul",{},[331,332,333,336],"li",{},[70,334,335],{},"Datenbank"," — dokumentorientiert mit Indizes, Volltextsuche und ACID-Transaktionen",[331,338,339,342],{},[70,340,341],{},"Vektorsuche"," — native Vektorindizes für Embedding-basierten Abruf (das Substrat, auf dem der Wissensgraph und die semantische Dateisuche aufbauen, während diese Funktionen reifen)",[331,344,345,348,349,352],{},[70,346,347],{},"Dateispeicher"," — Up- und Downloads von Binärdateien über ",[38,350,351],{},"ctx.storage"," (Medienbibliothek, Anhänge, semantische Dateien)",[331,354,355,358],{},[70,356,357],{},"Echtzeit-Subscriptions"," — reaktive Queries, die Aktualisierungen über WebSocket an die Oberfläche pushen",[331,360,361,364],{},[70,362,363],{},"Geplante Funktionen"," — Cronjobs und einmalig geplante Aufgaben (Kampagnenversand, Analytics-Reporting, Aufräumen der Versandreputation, Wissensverfall)",[331,366,367,370],{},[70,368,369],{},"HTTP-Actions"," — Webhook-Endpunkte, API-Routen, Tracking-Pixel",[15,372,373,374,377],{},"Das bedeutet: ",[70,375,376],{},"kein PostgreSQL, kein MinIO, keine separate Vektordatenbank",". Das Convex-Backend ist der einzige zustandsbehaftete Dienst.",[27,379,381],{"id":380},"docker-compose","Docker Compose",[15,383,384,385,387],{},"Die maßgebliche Definition liegt in der ",[38,386,40],{}," im Wurzelverzeichnis des Repositorys. Der folgende Auszug zeigt die Verdrahtung der Kerndienste — für den vollständigen Satz an Build-Argumenten, Health Checks und Umgebungsvariablen zur Versionsfixierung ziehen Sie bitte die tatsächliche Datei heran.",[389,390,395],"pre",{"className":391,"code":392,"language":393,"meta":394,"style":394},"language-yaml shiki shiki-themes github-light github-dark-dimmed","services:\n  # Database + serverless functions\n  convex:\n    image: ghcr.io\u002Fget-convex\u002Fconvex-backend:${CONVEX_BACKEND_VERSION:-...}\n    ports:\n      - \"${CONVEX_PORT:-3210}:3210\"        # Backend API\n      - \"${CONVEX_SITE_PORT:-3211}:3211\"   # HTTP actions \u002F site proxy\n    volumes:\n      - convex-data:\u002Fconvex\u002Fdata\n    environment:\n      INSTANCE_SECRET: ${INSTANCE_SECRET}\n      CONVEX_CLOUD_ORIGIN: ${NUXT_PUBLIC_CONVEX_URL:-http:\u002F\u002Flocalhost:3210}\n      CONVEX_SITE_ORIGIN: ${NUXT_PUBLIC_CONVEX_SITE_URL:-http:\u002F\u002Flocalhost:3211}\n\n  # Admin\u002Fdebugging UI (separate service, port 6791)\n  convex-dashboard:\n    image: ghcr.io\u002Fget-convex\u002Fconvex-dashboard:${CONVEX_DASHBOARD_VERSION:-...}\n    ports:\n      - \"${DASHBOARD_PORT:-6791}:6791\"\n    environment:\n      CONVEX_URL: http:\u002F\u002Fconvex:3210\n\n  # Nuxt web app\n  web:\n    image: ghcr.io\u002Fwolvesdotink\u002Fweb:${OWLAT_VERSION:-latest}\n    ports:\n      - \"${WEB_PORT:-3000}:3000\"\n    environment:\n      NUXT_PUBLIC_CONVEX_URL: ${NUXT_PUBLIC_CONVEX_URL:-http:\u002F\u002Flocalhost:3210}\n      NUXT_PUBLIC_CONVEX_SITE_URL: ${NUXT_PUBLIC_CONVEX_SITE_URL:-http:\u002F\u002Flocalhost:3211}\n      NUXT_PUBLIC_SITE_URL: ${NUXT_PUBLIC_SITE_URL:-http:\u002F\u002Flocalhost:3000}\n      OWLAT_DEPLOYMENT_MODE: ${OWLAT_DEPLOYMENT_MODE:-selfhost}\n      INSTANCE_SECRET: ${INSTANCE_SECRET}   # for calling the updater sidecar\n\n  mta:\n    image: ghcr.io\u002Fwolvesdotink\u002Fmta:${OWLAT_VERSION:-latest}\n    ports:\n      - \"${MTA_SMTP_PORT:-25}:25\"     # Inbound SMTP (bounce processing)\n      - \"${MTA_HTTP_PORT:-3100}:3100\" # HTTP API\n    environment:\n      REDIS_URL: redis:\u002F\u002Fredis:6379\n      CONVEX_SITE_URL: http:\u002F\u002Fconvex:3211\n      MTA_API_KEY: ${MTA_API_KEY}\n      MTA_WEBHOOK_SECRET: ${MTA_WEBHOOK_SECRET}\n\n  redis:\n    image: redis:${REDIS_VERSION:-7.4-alpine}\n    command: redis-server --appendonly yes\n    volumes:\n      - redis-data:\u002Fdata\n\n  clamav:\n    image: clamav\u002Fclamav:${CLAMAV_VERSION:-stable}\n    volumes:\n      - clamav-data:\u002Fvar\u002Flib\u002Fclamav\n\n  # In-app update sidecar (reached only via the internal network)\n  updater:\n    image: ghcr.io\u002Fwolvesdotink\u002Fupdater:${OWLAT_VERSION:-latest}\n    # mounts only the install dir; reaches Docker through the least-privilege docker-socket-proxy (DOCKER_HOST), not the raw socket\n\n  # Reverse proxy with automatic HTTPS (production)\n  caddy:\n    image: caddy:${CADDY_VERSION:-2.8-alpine}\n    ports: [\"80:80\", \"443:443\"]\n    profiles: [tls]\n\n  # One-shot: push tenant functions to the backend after first boot\n  convex-deploy:\n    image: ghcr.io\u002Fwolvesdotink\u002Fconvex-deploy:${OWLAT_VERSION:-latest}\n    environment:\n      CONVEX_SELF_HOSTED_URL: http:\u002F\u002Fconvex:3210\n      CONVEX_SELF_HOSTED_ADMIN_KEY: ${CONVEX_ADMIN_KEY}\n    profiles: [deploy]\n\n  # AI coding-agent sidecar (feature request → prototype)\n  code-worker:\n    build: { context: ., dockerfile: apps\u002Fcode-worker\u002FDockerfile }\n    profiles: [inbox-codetasks, dev]\n\nvolumes:\n  convex-data:\n  redis-data:\n  clamav-data:\n  code-workspace:\n  caddy-data:\n  caddy-config:\n","yaml","",[38,396,397,410,417,425,438,446,458,469,477,485,493,504,515,526,533,539,547,557,564,572,579,590,595,601,609,619,626,634,641,651,661,672,683,696,701,709,719,726,737,748,755,766,777,788,799,804,812,822,833,840,848,853,861,871,878,886,891,897,905,915,921,926,932,940,950,970,983,988,994,1002,1012,1019,1029,1040,1052,1057,1063,1071,1101,1117,1122,1130,1138,1146,1154,1162,1170],{"__ignoreMap":394},[398,399,402,406],"span",{"class":400,"line":401},"line",1,[398,403,405],{"class":404},"sIAta","services",[398,407,409],{"class":408},"sYgZi",":\n",[398,411,413],{"class":400,"line":412},2,[398,414,416],{"class":415},"sDN9O","  # Database + serverless functions\n",[398,418,420,423],{"class":400,"line":419},3,[398,421,422],{"class":404},"  convex",[398,424,409],{"class":408},[398,426,428,431,434],{"class":400,"line":427},4,[398,429,430],{"class":404},"    image",[398,432,433],{"class":408},": ",[398,435,437],{"class":436},"s-HuK","ghcr.io\u002Fget-convex\u002Fconvex-backend:${CONVEX_BACKEND_VERSION:-...}\n",[398,439,441,444],{"class":400,"line":440},5,[398,442,443],{"class":404},"    ports",[398,445,409],{"class":408},[398,447,449,452,455],{"class":400,"line":448},6,[398,450,451],{"class":408},"      - ",[398,453,454],{"class":436},"\"${CONVEX_PORT:-3210}:3210\"",[398,456,457],{"class":415},"        # Backend API\n",[398,459,461,463,466],{"class":400,"line":460},7,[398,462,451],{"class":408},[398,464,465],{"class":436},"\"${CONVEX_SITE_PORT:-3211}:3211\"",[398,467,468],{"class":415},"   # HTTP actions \u002F site proxy\n",[398,470,472,475],{"class":400,"line":471},8,[398,473,474],{"class":404},"    volumes",[398,476,409],{"class":408},[398,478,480,482],{"class":400,"line":479},9,[398,481,451],{"class":408},[398,483,484],{"class":436},"convex-data:\u002Fconvex\u002Fdata\n",[398,486,488,491],{"class":400,"line":487},10,[398,489,490],{"class":404},"    environment",[398,492,409],{"class":408},[398,494,496,499,501],{"class":400,"line":495},11,[398,497,498],{"class":404},"      INSTANCE_SECRET",[398,500,433],{"class":408},[398,502,503],{"class":436},"${INSTANCE_SECRET}\n",[398,505,507,510,512],{"class":400,"line":506},12,[398,508,509],{"class":404},"      CONVEX_CLOUD_ORIGIN",[398,511,433],{"class":408},[398,513,514],{"class":436},"${NUXT_PUBLIC_CONVEX_URL:-http:\u002F\u002Flocalhost:3210}\n",[398,516,518,521,523],{"class":400,"line":517},13,[398,519,520],{"class":404},"      CONVEX_SITE_ORIGIN",[398,522,433],{"class":408},[398,524,525],{"class":436},"${NUXT_PUBLIC_CONVEX_SITE_URL:-http:\u002F\u002Flocalhost:3211}\n",[398,527,529],{"class":400,"line":528},14,[398,530,532],{"emptyLinePlaceholder":531},true,"\n",[398,534,536],{"class":400,"line":535},15,[398,537,538],{"class":415},"  # Admin\u002Fdebugging UI (separate service, port 6791)\n",[398,540,542,545],{"class":400,"line":541},16,[398,543,544],{"class":404},"  convex-dashboard",[398,546,409],{"class":408},[398,548,550,552,554],{"class":400,"line":549},17,[398,551,430],{"class":404},[398,553,433],{"class":408},[398,555,556],{"class":436},"ghcr.io\u002Fget-convex\u002Fconvex-dashboard:${CONVEX_DASHBOARD_VERSION:-...}\n",[398,558,560,562],{"class":400,"line":559},18,[398,561,443],{"class":404},[398,563,409],{"class":408},[398,565,567,569],{"class":400,"line":566},19,[398,568,451],{"class":408},[398,570,571],{"class":436},"\"${DASHBOARD_PORT:-6791}:6791\"\n",[398,573,575,577],{"class":400,"line":574},20,[398,576,490],{"class":404},[398,578,409],{"class":408},[398,580,582,585,587],{"class":400,"line":581},21,[398,583,584],{"class":404},"      CONVEX_URL",[398,586,433],{"class":408},[398,588,589],{"class":436},"http:\u002F\u002Fconvex:3210\n",[398,591,593],{"class":400,"line":592},22,[398,594,532],{"emptyLinePlaceholder":531},[398,596,598],{"class":400,"line":597},23,[398,599,600],{"class":415},"  # Nuxt web app\n",[398,602,604,607],{"class":400,"line":603},24,[398,605,606],{"class":404},"  web",[398,608,409],{"class":408},[398,610,612,614,616],{"class":400,"line":611},25,[398,613,430],{"class":404},[398,615,433],{"class":408},[398,617,618],{"class":436},"ghcr.io\u002Fwolvesdotink\u002Fweb:${OWLAT_VERSION:-latest}\n",[398,620,622,624],{"class":400,"line":621},26,[398,623,443],{"class":404},[398,625,409],{"class":408},[398,627,629,631],{"class":400,"line":628},27,[398,630,451],{"class":408},[398,632,633],{"class":436},"\"${WEB_PORT:-3000}:3000\"\n",[398,635,637,639],{"class":400,"line":636},28,[398,638,490],{"class":404},[398,640,409],{"class":408},[398,642,644,647,649],{"class":400,"line":643},29,[398,645,646],{"class":404},"      NUXT_PUBLIC_CONVEX_URL",[398,648,433],{"class":408},[398,650,514],{"class":436},[398,652,654,657,659],{"class":400,"line":653},30,[398,655,656],{"class":404},"      NUXT_PUBLIC_CONVEX_SITE_URL",[398,658,433],{"class":408},[398,660,525],{"class":436},[398,662,664,667,669],{"class":400,"line":663},31,[398,665,666],{"class":404},"      NUXT_PUBLIC_SITE_URL",[398,668,433],{"class":408},[398,670,671],{"class":436},"${NUXT_PUBLIC_SITE_URL:-http:\u002F\u002Flocalhost:3000}\n",[398,673,675,678,680],{"class":400,"line":674},32,[398,676,677],{"class":404},"      OWLAT_DEPLOYMENT_MODE",[398,679,433],{"class":408},[398,681,682],{"class":436},"${OWLAT_DEPLOYMENT_MODE:-selfhost}\n",[398,684,686,688,690,693],{"class":400,"line":685},33,[398,687,498],{"class":404},[398,689,433],{"class":408},[398,691,692],{"class":436},"${INSTANCE_SECRET}",[398,694,695],{"class":415},"   # for calling the updater sidecar\n",[398,697,699],{"class":400,"line":698},34,[398,700,532],{"emptyLinePlaceholder":531},[398,702,704,707],{"class":400,"line":703},35,[398,705,706],{"class":404},"  mta",[398,708,409],{"class":408},[398,710,712,714,716],{"class":400,"line":711},36,[398,713,430],{"class":404},[398,715,433],{"class":408},[398,717,718],{"class":436},"ghcr.io\u002Fwolvesdotink\u002Fmta:${OWLAT_VERSION:-latest}\n",[398,720,722,724],{"class":400,"line":721},37,[398,723,443],{"class":404},[398,725,409],{"class":408},[398,727,729,731,734],{"class":400,"line":728},38,[398,730,451],{"class":408},[398,732,733],{"class":436},"\"${MTA_SMTP_PORT:-25}:25\"",[398,735,736],{"class":415},"     # Inbound SMTP (bounce processing)\n",[398,738,740,742,745],{"class":400,"line":739},39,[398,741,451],{"class":408},[398,743,744],{"class":436},"\"${MTA_HTTP_PORT:-3100}:3100\"",[398,746,747],{"class":415}," # HTTP API\n",[398,749,751,753],{"class":400,"line":750},40,[398,752,490],{"class":404},[398,754,409],{"class":408},[398,756,758,761,763],{"class":400,"line":757},41,[398,759,760],{"class":404},"      REDIS_URL",[398,762,433],{"class":408},[398,764,765],{"class":436},"redis:\u002F\u002Fredis:6379\n",[398,767,769,772,774],{"class":400,"line":768},42,[398,770,771],{"class":404},"      CONVEX_SITE_URL",[398,773,433],{"class":408},[398,775,776],{"class":436},"http:\u002F\u002Fconvex:3211\n",[398,778,780,783,785],{"class":400,"line":779},43,[398,781,782],{"class":404},"      MTA_API_KEY",[398,784,433],{"class":408},[398,786,787],{"class":436},"${MTA_API_KEY}\n",[398,789,791,794,796],{"class":400,"line":790},44,[398,792,793],{"class":404},"      MTA_WEBHOOK_SECRET",[398,795,433],{"class":408},[398,797,798],{"class":436},"${MTA_WEBHOOK_SECRET}\n",[398,800,802],{"class":400,"line":801},45,[398,803,532],{"emptyLinePlaceholder":531},[398,805,807,810],{"class":400,"line":806},46,[398,808,809],{"class":404},"  redis",[398,811,409],{"class":408},[398,813,815,817,819],{"class":400,"line":814},47,[398,816,430],{"class":404},[398,818,433],{"class":408},[398,820,821],{"class":436},"redis:${REDIS_VERSION:-7.4-alpine}\n",[398,823,825,828,830],{"class":400,"line":824},48,[398,826,827],{"class":404},"    command",[398,829,433],{"class":408},[398,831,832],{"class":436},"redis-server --appendonly yes\n",[398,834,836,838],{"class":400,"line":835},49,[398,837,474],{"class":404},[398,839,409],{"class":408},[398,841,843,845],{"class":400,"line":842},50,[398,844,451],{"class":408},[398,846,847],{"class":436},"redis-data:\u002Fdata\n",[398,849,851],{"class":400,"line":850},51,[398,852,532],{"emptyLinePlaceholder":531},[398,854,856,859],{"class":400,"line":855},52,[398,857,858],{"class":404},"  clamav",[398,860,409],{"class":408},[398,862,864,866,868],{"class":400,"line":863},53,[398,865,430],{"class":404},[398,867,433],{"class":408},[398,869,870],{"class":436},"clamav\u002Fclamav:${CLAMAV_VERSION:-stable}\n",[398,872,874,876],{"class":400,"line":873},54,[398,875,474],{"class":404},[398,877,409],{"class":408},[398,879,881,883],{"class":400,"line":880},55,[398,882,451],{"class":408},[398,884,885],{"class":436},"clamav-data:\u002Fvar\u002Flib\u002Fclamav\n",[398,887,889],{"class":400,"line":888},56,[398,890,532],{"emptyLinePlaceholder":531},[398,892,894],{"class":400,"line":893},57,[398,895,896],{"class":415},"  # In-app update sidecar (reached only via the internal network)\n",[398,898,900,903],{"class":400,"line":899},58,[398,901,902],{"class":404},"  updater",[398,904,409],{"class":408},[398,906,908,910,912],{"class":400,"line":907},59,[398,909,430],{"class":404},[398,911,433],{"class":408},[398,913,914],{"class":436},"ghcr.io\u002Fwolvesdotink\u002Fupdater:${OWLAT_VERSION:-latest}\n",[398,916,918],{"class":400,"line":917},60,[398,919,920],{"class":415},"    # mounts only the install dir; reaches Docker through the least-privilege docker-socket-proxy (DOCKER_HOST), not the raw socket\n",[398,922,924],{"class":400,"line":923},61,[398,925,532],{"emptyLinePlaceholder":531},[398,927,929],{"class":400,"line":928},62,[398,930,931],{"class":415},"  # Reverse proxy with automatic HTTPS (production)\n",[398,933,935,938],{"class":400,"line":934},63,[398,936,937],{"class":404},"  caddy",[398,939,409],{"class":408},[398,941,943,945,947],{"class":400,"line":942},64,[398,944,430],{"class":404},[398,946,433],{"class":408},[398,948,949],{"class":436},"caddy:${CADDY_VERSION:-2.8-alpine}\n",[398,951,953,955,958,961,964,967],{"class":400,"line":952},65,[398,954,443],{"class":404},[398,956,957],{"class":408},": [",[398,959,960],{"class":436},"\"80:80\"",[398,962,963],{"class":408},", ",[398,965,966],{"class":436},"\"443:443\"",[398,968,969],{"class":408},"]\n",[398,971,973,976,978,981],{"class":400,"line":972},66,[398,974,975],{"class":404},"    profiles",[398,977,957],{"class":408},[398,979,980],{"class":436},"tls",[398,982,969],{"class":408},[398,984,986],{"class":400,"line":985},67,[398,987,532],{"emptyLinePlaceholder":531},[398,989,991],{"class":400,"line":990},68,[398,992,993],{"class":415},"  # One-shot: push tenant functions to the backend after first boot\n",[398,995,997,1000],{"class":400,"line":996},69,[398,998,999],{"class":404},"  convex-deploy",[398,1001,409],{"class":408},[398,1003,1005,1007,1009],{"class":400,"line":1004},70,[398,1006,430],{"class":404},[398,1008,433],{"class":408},[398,1010,1011],{"class":436},"ghcr.io\u002Fwolvesdotink\u002Fconvex-deploy:${OWLAT_VERSION:-latest}\n",[398,1013,1015,1017],{"class":400,"line":1014},71,[398,1016,490],{"class":404},[398,1018,409],{"class":408},[398,1020,1022,1025,1027],{"class":400,"line":1021},72,[398,1023,1024],{"class":404},"      CONVEX_SELF_HOSTED_URL",[398,1026,433],{"class":408},[398,1028,589],{"class":436},[398,1030,1032,1035,1037],{"class":400,"line":1031},73,[398,1033,1034],{"class":404},"      CONVEX_SELF_HOSTED_ADMIN_KEY",[398,1036,433],{"class":408},[398,1038,1039],{"class":436},"${CONVEX_ADMIN_KEY}\n",[398,1041,1043,1045,1047,1050],{"class":400,"line":1042},74,[398,1044,975],{"class":404},[398,1046,957],{"class":408},[398,1048,1049],{"class":436},"deploy",[398,1051,969],{"class":408},[398,1053,1055],{"class":400,"line":1054},75,[398,1056,532],{"emptyLinePlaceholder":531},[398,1058,1060],{"class":400,"line":1059},76,[398,1061,1062],{"class":415},"  # AI coding-agent sidecar (feature request → prototype)\n",[398,1064,1066,1069],{"class":400,"line":1065},77,[398,1067,1068],{"class":404},"  code-worker",[398,1070,409],{"class":408},[398,1072,1074,1077,1080,1083,1085,1089,1091,1094,1096,1098],{"class":400,"line":1073},78,[398,1075,1076],{"class":404},"    build",[398,1078,1079],{"class":408},": { ",[398,1081,1082],{"class":404},"context",[398,1084,433],{"class":408},[398,1086,1088],{"class":1087},"sviXB",".",[398,1090,963],{"class":408},[398,1092,1093],{"class":404},"dockerfile",[398,1095,433],{"class":408},[398,1097,262],{"class":436},[398,1099,1100],{"class":408}," }\n",[398,1102,1104,1106,1108,1111,1113,1115],{"class":400,"line":1103},79,[398,1105,975],{"class":404},[398,1107,957],{"class":408},[398,1109,1110],{"class":436},"inbox-codetasks",[398,1112,963],{"class":408},[398,1114,253],{"class":436},[398,1116,969],{"class":408},[398,1118,1120],{"class":400,"line":1119},80,[398,1121,532],{"emptyLinePlaceholder":531},[398,1123,1125,1128],{"class":400,"line":1124},81,[398,1126,1127],{"class":404},"volumes",[398,1129,409],{"class":408},[398,1131,1133,1136],{"class":400,"line":1132},82,[398,1134,1135],{"class":404},"  convex-data",[398,1137,409],{"class":408},[398,1139,1141,1144],{"class":400,"line":1140},83,[398,1142,1143],{"class":404},"  redis-data",[398,1145,409],{"class":408},[398,1147,1149,1152],{"class":400,"line":1148},84,[398,1150,1151],{"class":404},"  clamav-data",[398,1153,409],{"class":408},[398,1155,1157,1160],{"class":400,"line":1156},85,[398,1158,1159],{"class":404},"  code-workspace",[398,1161,409],{"class":408},[398,1163,1165,1168],{"class":400,"line":1164},86,[398,1166,1167],{"class":404},"  caddy-data",[398,1169,409],{"class":408},[398,1171,1173,1176],{"class":400,"line":1172},87,[398,1174,1175],{"class":404},"  caddy-config",[398,1177,409],{"class":408},[19,1179,1181],{"title":1180,"type":22},"Umgebungsvariablen des Web-Dienstes",[15,1182,1183,1184,1187,1188,1191,1192,1195,1196,1199,1200,1203],{},"Der Dienst ",[38,1185,1186],{},"web"," liest die browserseitigen ",[38,1189,1190],{},"NUXT_PUBLIC_*","-URLs. ",[38,1193,1194],{},"CONVEX_SELF_HOSTED_URL"," \u002F ",[38,1197,1198],{},"CONVEX_SELF_HOSTED_ADMIN_KEY"," werden ausschließlich vom Einmal-Dienst ",[38,1201,1202],{},"convex-deploy"," zum Pushen der Funktionen genutzt, nicht von der laufenden Web-App.",[27,1205,1207],{"id":1206},"umgebungsvariablen","Umgebungsvariablen",[15,1209,1210,1211,1214,1215,1218,1219,1222,1223,1226,1227,1230,1231,1234],{},"Owlat teilt seine Konfiguration auf in ",[70,1212,1213],{},"Compose-Variablen"," (in ",[38,1216,1217],{},".env",", kopiert aus ",[38,1220,1221],{},".env.selfhost.example","), die die Container miteinander verdrahten, und ",[70,1224,1225],{},"Convex-Funktions","-Variablen, die über ",[38,1228,1229],{},"convex env set"," am Backend gesetzt werden (oder von ",[38,1232,1233],{},"owlat quickstart"," für Sie angewendet werden). Die folgenden Tabellen decken beides ab — die Spalte „Geltungsbereich“ sagt Ihnen, wo die jeweilige Variable lebt.",[315,1236,1238],{"id":1237},"erforderlich","Erforderlich",[43,1240,1241,1254],{},[46,1242,1243],{},[49,1244,1245,1248,1251],{},[52,1246,1247],{},"Variable",[52,1249,1250],{},"Geltungsbereich",[52,1252,1253],{},"Beschreibung",[62,1255,1256,1274,1291,1305,1319,1336,1353,1369],{},[49,1257,1258,1263,1268],{},[67,1259,1260],{},[38,1261,1262],{},"INSTANCE_SECRET",[67,1264,1265,1266],{},"Compose ",[38,1267,1217],{},[67,1269,1270,1271],{},"Instanz-Secret des Convex-Backends — ",[38,1272,1273],{},"openssl rand -hex 32",[49,1275,1276,1281,1285],{},[67,1277,1278],{},[38,1279,1280],{},"CONVEX_ADMIN_KEY",[67,1282,1265,1283],{},[38,1284,1217],{},[67,1286,1287,1288],{},"Admin-Key, nach dem ersten Start erzeugt über ",[38,1289,1290],{},"docker compose exec convex .\u002Fgenerate_admin_key.sh",[49,1292,1293,1298,1302],{},[67,1294,1295],{},[38,1296,1297],{},"MTA_API_KEY",[67,1299,1265,1300],{},[38,1301,1217],{},[67,1303,1304],{},"Gemeinsames Secret zwischen Convex und MTA für Versandanfragen",[49,1306,1307,1312,1316],{},[67,1308,1309],{},[38,1310,1311],{},"MTA_WEBHOOK_SECRET",[67,1313,1265,1314],{},[38,1315,1217],{},[67,1317,1318],{},"HMAC-Secret für die Authentifizierung der Webhooks MTA → Convex",[49,1320,1321,1326,1330],{},[67,1322,1323],{},[38,1324,1325],{},"NUXT_PUBLIC_SITE_URL",[67,1327,1265,1328],{},[38,1329,1217],{},[67,1331,1332,1333,96],{},"Öffentliche, browserseitige URL der Web-App (z. B. ",[38,1334,1335],{},"https:\u002F\u002Fowlat.example.com",[49,1337,1338,1346,1350],{},[67,1339,1340,1195,1343],{},[38,1341,1342],{},"NUXT_PUBLIC_CONVEX_URL",[38,1344,1345],{},"NUXT_PUBLIC_CONVEX_SITE_URL",[67,1347,1265,1348],{},[38,1349,1217],{},[67,1351,1352],{},"Öffentliche URLs von Convex-Backend und Site-Proxy",[49,1354,1355,1360,1363],{},[67,1356,1357],{},[38,1358,1359],{},"BETTER_AUTH_SECRET",[67,1361,1362],{},"Convex-Funktion",[67,1364,1365,1366,1368],{},"Secret zum Signieren der Sessions in der Auth-Schicht (mit ",[38,1367,1229],{}," setzen)",[49,1370,1371,1376,1378],{},[67,1372,1373],{},[38,1374,1375],{},"UNSUBSCRIBE_SECRET",[67,1377,1362],{},[67,1379,1380,1381,1368],{},"Eigenes Secret zum Signieren von Abmelde-\u002FPräferenz-Links (mit ",[38,1382,1229],{},[15,1384,1385,1386,1388,1389,1391,1392,1394],{},"Die Datei ",[38,1387,1221],{}," im Wurzelverzeichnis des Repositorys listet jede Compose-Variable auf. Die Convex-Funktions-Secrets werden von ",[38,1390,1233],{}," automatisch gesetzt oder von Hand mit ",[38,1393,1229],{},", sobald das Backend läuft.",[19,1396,1398],{"title":1397,"type":22},"SITE_URL vs. NUXT_PUBLIC_SITE_URL",[15,1399,1400,1403,1404,1407,1408,1411,1412,1414,1415,1417,1418,1420,1421,1423],{},[38,1401,1402],{},"SITE_URL"," ist eine Umgebungsvariable der ",[70,1405,1406],{},"Convex-Funktionen"," (",[38,1409,1410],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002Fenv.ts","), die Backend-Funktionen zum Bauen absoluter Links verwenden. Der ",[70,1413,115],{},"-Container nutzt die browserseitige ",[38,1416,1325],{},". Beide sind verschieden — setzen Sie beide. (",[38,1419,1233],{}," setzt ",[38,1422,1402],{}," für Sie.)",[15,1425,1426,1427,1430,1431,1088],{},"Das gehärtete VPS-Template (",[38,1428,1429],{},"infra\u002Ftemplates\u002F.env.vps.template",") verlangt zusätzlich ",[38,1432,1433],{},"REDIS_PASSWORD",[315,1435,1437],{"id":1436},"llm-konfiguration-für-die-agenten-strecke","LLM-Konfiguration (für die Agenten-Strecke)",[15,1439,1440,1441,1443,1444,1446,1447,1088],{},"Dies sind Variablen der ",[70,1442,1406],{}," (mit ",[38,1445,1229],{}," setzen). Details finden Sie in ",[309,1448,1450],{"href":1449},"\u002Fdeveloper\u002Fdecisions\u002F007-pluggable-llm","ADR-007: Austauschbarer LLM-Anbieter",[43,1452,1453,1464],{},[46,1454,1455],{},[49,1456,1457,1459,1461],{},[52,1458,1247],{},[52,1460,1253],{},[52,1462,1463],{},"Standard",[62,1465,1466,1502,1516,1536],{},[49,1467,1468,1473,1498],{},[67,1469,1470],{},[38,1471,1472],{},"LLM_PROVIDER",[67,1474,1475,1476,1479,1480,1483,1484,1487,1488,1491,1492,1494,1495,1088],{},"Anbietertyp: ",[38,1477,1478],{},"openai"," (Standard), ",[38,1481,1482],{},"openrouter"," oder ",[38,1485,1486],{},"ollama"," (alle OpenAI-kompatibel). ",[38,1489,1490],{},"anthropic"," ist kein anerkannter Wert — betreiben Sie Claude über ",[38,1493,1478],{}," mit einer OpenAI-kompatiblen ",[38,1496,1497],{},"LLM_BASE_URL",[67,1499,1500],{},[38,1501,1478],{},[49,1503,1504,1508,1513],{},[67,1505,1506],{},[38,1507,1497],{},[67,1509,1510,1511,96],{},"Überschreibt den API-Endpunkt (für Ollama: ",[38,1512,306],{},[67,1514,1515],{},"Standard des Anbieters",[49,1517,1518,1523,1533],{},[67,1519,1520],{},[38,1521,1522],{},"LLM_API_KEY",[67,1524,1525,1526,1195,1529,1532],{},"API-Key — ",[38,1527,1528],{},"OPENROUTER_API_KEY",[38,1530,1531],{},"OPENAI_API_KEY"," werden ebenfalls akzeptiert; der zuerst gesetzte gewinnt",[67,1534,1535],{},"— (für Ollama nicht nötig)",[49,1537,1538,1543,1553],{},[67,1539,1540],{},[38,1541,1542],{},"LLM_MODEL",[67,1544,1545,1546,1195,1549,1552],{},"Standard-Modellbezeichner (oder ",[38,1547,1548],{},"LLM_MODEL_FAST",[38,1550,1551],{},"LLM_MODEL_CAPABLE"," je Stufe)",[67,1554,1555],{},[38,1556,1557],{},"gpt-4o",[315,1559,1561],{"id":1560},"optional","Optional",[43,1563,1564,1574],{},[46,1565,1566],{},[49,1567,1568,1570,1572],{},[52,1569,1247],{},[52,1571,1253],{},[52,1573,1463],{},[62,1575,1576,1591,1622,1637,1652],{},[49,1577,1578,1583,1586],{},[67,1579,1580],{},[38,1581,1582],{},"OWLAT_DEPLOYMENT_MODE",[67,1584,1585],{},"Blendet nur für Hosted gedachte Oberflächen aus; zeigt das Onboarding-Banner für Self-Hosting",[67,1587,1588],{},[38,1589,1590],{},"selfhost",[49,1592,1593,1598,1619],{},[67,1594,1595],{},[38,1596,1597],{},"EMAIL_PROVIDER",[67,1599,1600,1601,963,1604,963,1607,963,1610,963,1613,963,1616],{},"E-Mail-Anbieter: ",[38,1602,1603],{},"mta",[38,1605,1606],{},"ses",[38,1608,1609],{},"resend",[38,1611,1612],{},"smtp",[38,1614,1615],{},"mandrill",[38,1617,1618],{},"emailit",[67,1620,1621],{},"— (nicht gesetzt ⇒ unkonfiguriert; Sendungen werden abgelehnt)",[49,1623,1624,1629,1632],{},[67,1625,1626],{},[38,1627,1628],{},"CLAMAV_HOST",[67,1630,1631],{},"ClamAV-Hostname",[67,1633,1634],{},[38,1635,1636],{},"clamav",[49,1638,1639,1644,1647],{},[67,1640,1641],{},[38,1642,1643],{},"CLAMAV_PORT",[67,1645,1646],{},"ClamAV-Port",[67,1648,1649],{},[38,1650,1651],{},"3310",[49,1653,1654,1659,1676],{},[67,1655,1656],{},[38,1657,1658],{},"GITHUB_WEBHOOK_SECRET",[67,1660,1661,1662,963,1665,1668,1669,1672,1673,1675],{},"Convex-Funktions-Variable. HMAC-Secret für den Webhook zum GitHub-PR-Merge (",[38,1663,1664],{},"POST \u002Fwebhooks\u002Fgithub",[38,1666,1667],{},"apps\u002Fapi\u002Fconvex\u002Fwebhooks\u002FgithubHttp.ts","), der eine Code-Aufgabe auf ",[38,1670,1671],{},"merged"," schaltet, sobald ihr PR landet. Nicht gesetzt deaktiviert den Endpunkt (liefert 503); mit ",[38,1674,1229],{}," setzen.",[67,1677,1678],{},"—",[27,1680,1682],{"id":1681},"wie-der-stack-sich-weiterentwickelt","Wie der Stack sich weiterentwickelt",[15,1684,1685,1686,1690],{},"Die Docker-Compose-Datei wächst mit jeder Phase der ",[309,1687,1689],{"href":1688},"\u002Fvision","Roadmap",":",[43,1692,1693,1706],{},[46,1694,1695],{},[49,1696,1697,1700,1703],{},[52,1698,1699],{},"Phase",[52,1701,1702],{},"Hinzugekommene Dienste",[52,1704,1705],{},"Zweck",[62,1707,1708,1722,1735,1748],{},[49,1709,1710,1716,1719],{},[67,1711,1712,1715],{},[70,1713,1714],{},"Jetzt"," (E-Mail-Plattform)",[67,1717,1718],{},"convex, convex-dashboard, web, mta, redis, clamav, updater",[67,1720,1721],{},"Vollständige E-Mail-Marketing-Plattform",[49,1723,1724,1730,1732],{},[67,1725,1726,1729],{},[70,1727,1728],{},"Als Nächstes"," (Eingang & Agenten)",[67,1731,1678],{},[67,1733,1734],{},"Agenten-Strecke, Verarbeitung eingehender Mail, Prüf-Warteschlange (alles Convex-Funktionen)",[49,1736,1737,1743,1745],{},[67,1738,1739,1742],{},[70,1740,1741],{},"Danach"," (Kommunikationsintelligenz)",[67,1744,1678],{},[67,1746,1747],{},"Wissensgraph, Multi-Channel, CRM, Dateisystem — alles läuft innerhalb von Convex",[49,1749,1750,1756,1765],{},[67,1751,1752,1755],{},[70,1753,1754],{},"Später"," (Vollständige Vision)",[67,1757,1758,1759],{},"code-worker ",[90,1760,92,1761,250,1763,96],{},[38,1762,249],{},[38,1764,253],{},[67,1766,1767],{},"Coding-Agent-Sidecar für die Strecke Feature-Wunsch → Prototyp",[15,1769,1770,1771,1774,1775,1778,1779,1781,1782,1784],{},"Die Architektur ist so entworfen, dass ",[70,1772,1773],{},"das Hinzufügen von KI-Fähigkeiten keine zusätzliche erforderliche Infrastruktur nach sich zieht",". Agenten-Strecke, Wissensgraph und semantische Suche laufen allesamt als Convex-Funktionen. Das LLM selbst ist eine Konfigurationsentscheidung, kein ",[90,1776,1777],{},"erforderlicher"," Dienst — nutzen Sie einen gehosteten OpenAI-\u002FOpenRouter-\u002FAnthropic-Key oder lassen Sie die Inferenz lokal im mitgelieferten Ollama-Container laufen (",[38,1780,275],{},", auch unter ",[38,1783,298],{}," aktiviert).",[27,1786,1788],{"id":1787},"sicherheit-isolation","Sicherheit & Isolation",[15,1790,1791],{},"Self-Hosting heißt, KI-Agenten auf der eigenen Infrastruktur zu betreiben — was denselben Defense-in-Depth-Ansatz erfordert, der auch auf die E-Mail-Strecke angewendet wird. Das Sicherheitsmodell umfasst die Isolation von Zugangsdaten, das Sandboxing von Prozessen und Hygiene bei Umgebungsvariablen.",[315,1793,1795],{"id":1794},"isolation-von-zugangsdaten","Isolation von Zugangsdaten",[15,1797,1798],{},"Funktionen der Agenten-Strecke sehen niemals unmittelbar rohe API-Keys oder Secrets. Sämtliche sensible Konfiguration fließt durch das System für Umgebungsvariablen des Convex-Backends:",[328,1800,1801,1826,1852],{},[331,1802,1803,1806,1807,1810,1811,1814,1815,1818,1819,963,1821,1483,1823,1825],{},[70,1804,1805],{},"LLM-Zugangsdaten"," — der API-Key wird innerhalb von ",[38,1808,1809],{},"getLLMProvider()"," in ",[38,1812,1813],{},"apps\u002Fapi\u002Fconvex\u002Flib\u002FllmProvider.ts"," gelesen; der dortige Credential-Resolver ",[38,1816,1817],{},"resolveApiKey()"," akzeptiert ",[38,1820,1522],{},[38,1822,1528],{},[38,1824,1531],{}," (der zuerst gesetzte gewinnt). Der Key taucht nie im Agentenkontext, in LLM-Prompts oder in der Log-Ausgabe auf.",[331,1827,1828,1831,1832,1835,1836,1839,1840,1843,1844,1847,1848,1851],{},[70,1829,1830],{},"Zugangsdaten der Kanal-Anbieter"," — API-Keys von Kanälen (SMS\u002FWhatsApp\u002Fgenerisch) sind im Ruhezustand verschlüsselt: ",[38,1833,1834],{},"updateChannelConfig"," plant ",[38,1837,1838],{},"encryptAndPersistConfig"," ein (",[38,1841,1842],{},"apps\u002Fapi\u002Fconvex\u002Fchannels\u002Foutbound.ts","), das die Konfiguration über ",[38,1845,1846],{},"lib\u002FcredentialCrypto"," in einen AES-256-GCM-Umschlag hüllt, bevor sie in der Zeile ",[38,1849,1850],{},"channelConfigs"," landet. Entschlüsselt werden sie erst, wenn eine ausgehende Kanalfunktion sie für einen Aufruf beim Anbieter braucht.",[331,1853,1854,1857,1858,1861,1862,1864],{},[70,1855,1856],{},"Selbst gehostetes Ollama"," — betreiben Sie einen lokalen Ollama-Container, halten Sie ihn im internen Docker-Netzwerk (",[38,1859,1860],{},"ollama:11434",") ohne veröffentlichten Host-Port. Für Ollama ist kein API-Key erforderlich, und das Backend löst dessen Basis-URL automatisch auf, wenn ",[38,1863,302],{}," gesetzt ist.",[389,1866,1868],{"className":391,"code":1867,"language":393,"meta":394,"style":394},"# If you supply an Ollama container, leave it unpublished —\n# reachable only from other Docker services\nollama:\n  image: ollama\u002Follama\n  # No 'ports:' mapping — only accessible via the Docker network\n",[38,1869,1870,1875,1880,1886,1896],{"__ignoreMap":394},[398,1871,1872],{"class":400,"line":401},[398,1873,1874],{"class":415},"# If you supply an Ollama container, leave it unpublished —\n",[398,1876,1877],{"class":400,"line":412},[398,1878,1879],{"class":415},"# reachable only from other Docker services\n",[398,1881,1882,1884],{"class":400,"line":419},[398,1883,1486],{"class":404},[398,1885,409],{"class":408},[398,1887,1888,1891,1893],{"class":400,"line":427},[398,1889,1890],{"class":404},"  image",[398,1892,433],{"class":408},[398,1894,1895],{"class":436},"ollama\u002Follama\n",[398,1897,1898],{"class":400,"line":440},[398,1899,1900],{"class":415},"  # No 'ports:' mapping — only accessible via the Docker network\n",[315,1902,1904],{"id":1903},"prozess-sandboxing","Prozess-Sandboxing",[15,1906,1907],{},"Vom Agenten erzeugte Inhalte laufen in isolierten Ausführungsumgebungen:",[328,1909,1910,1924],{},[331,1911,1912,1915,1916,1919,1920,1088],{},[70,1913,1914],{},"Ausgabe des Visualisierungsagenten"," — gerendert in ",[38,1917,1918],{},"\u003Ciframe sandbox=\"allow-scripts\">"," ohne Zugriff auf das übergeordnete DOM, den Convex-Client, Cookies oder die Navigation. Siehe ",[309,1921,1923],{"href":1922},"\u002Fvision\u002Fdesktop-app#visualization-agent","Visualisierungsagent",[331,1925,1926,1929,1930,1933,1934,1936,1937,1939,1940,1943,1944,1947,1948,1951],{},[70,1927,1928],{},"Coding-Agent-Sidecar"," — der Container ",[38,1931,1932],{},"code-worker"," (betrieben unter ",[38,1935,249],{},", ebenfalls durch ",[38,1938,298],{}," aktiviert) erhält sein eigenes Volume ",[38,1941,1942],{},"code-workspace",", getrennt vom Volume ",[38,1945,1946],{},"convex-data"," des Backends, und läuft mit ",[38,1949,1950],{},"no-new-privileges",". Er spricht mit dem Backend ausschließlich über die Convex-API-URL.",[389,1953,1955],{"className":391,"code":1954,"language":393,"meta":394,"style":394},"code-worker:\n  build:\n    context: .\n    dockerfile: apps\u002Fcode-worker\u002FDockerfile\n  volumes:\n    - code-workspace:\u002Fworkspace   # Isolated workspace, not convex-data\n  environment:\n    CONVEX_URL: http:\u002F\u002Fconvex:3210\n    CONVEX_ADMIN_KEY: ${CONVEX_ADMIN_KEY} # authenticates internal-function calls\n  security_opt:\n    - no-new-privileges:true\n  profiles:\n    - inbox-codetasks\n    - dev\n",[38,1956,1957,1963,1970,1980,1990,1997,2008,2015,2024,2037,2044,2051,2058,2065],{"__ignoreMap":394},[398,1958,1959,1961],{"class":400,"line":401},[398,1960,1932],{"class":404},[398,1962,409],{"class":408},[398,1964,1965,1968],{"class":400,"line":412},[398,1966,1967],{"class":404},"  build",[398,1969,409],{"class":408},[398,1971,1972,1975,1977],{"class":400,"line":419},[398,1973,1974],{"class":404},"    context",[398,1976,433],{"class":408},[398,1978,1979],{"class":1087},".\n",[398,1981,1982,1985,1987],{"class":400,"line":427},[398,1983,1984],{"class":404},"    dockerfile",[398,1986,433],{"class":408},[398,1988,1989],{"class":436},"apps\u002Fcode-worker\u002FDockerfile\n",[398,1991,1992,1995],{"class":400,"line":440},[398,1993,1994],{"class":404},"  volumes",[398,1996,409],{"class":408},[398,1998,1999,2002,2005],{"class":400,"line":448},[398,2000,2001],{"class":408},"    - ",[398,2003,2004],{"class":436},"code-workspace:\u002Fworkspace",[398,2006,2007],{"class":415},"   # Isolated workspace, not convex-data\n",[398,2009,2010,2013],{"class":400,"line":460},[398,2011,2012],{"class":404},"  environment",[398,2014,409],{"class":408},[398,2016,2017,2020,2022],{"class":400,"line":471},[398,2018,2019],{"class":404},"    CONVEX_URL",[398,2021,433],{"class":408},[398,2023,589],{"class":436},[398,2025,2026,2029,2031,2034],{"class":400,"line":479},[398,2027,2028],{"class":404},"    CONVEX_ADMIN_KEY",[398,2030,433],{"class":408},[398,2032,2033],{"class":436},"${CONVEX_ADMIN_KEY}",[398,2035,2036],{"class":415}," # authenticates internal-function calls\n",[398,2038,2039,2042],{"class":400,"line":487},[398,2040,2041],{"class":404},"  security_opt",[398,2043,409],{"class":408},[398,2045,2046,2048],{"class":400,"line":495},[398,2047,2001],{"class":408},[398,2049,2050],{"class":436},"no-new-privileges:true\n",[398,2052,2053,2056],{"class":400,"line":506},[398,2054,2055],{"class":404},"  profiles",[398,2057,409],{"class":408},[398,2059,2060,2062],{"class":400,"line":517},[398,2061,2001],{"class":408},[398,2063,2064],{"class":436},"inbox-codetasks\n",[398,2066,2067,2069],{"class":400,"line":528},[398,2068,2001],{"class":408},[398,2070,2071],{"class":436},"dev\n",[315,2073,2075],{"id":2074},"hygiene-bei-umgebungsvariablen","Hygiene bei Umgebungsvariablen",[15,2077,2078],{},"Für sensible Variablen gelten strikte Geltungsbereichsregeln:",[43,2080,2081,2094],{},[46,2082,2083],{},[49,2084,2085,2088,2091],{},[52,2086,2087],{},"Geltungsbereich der Variable",[52,2089,2090],{},"Wer sie lesen darf",[52,2092,2093],{},"Beispiel",[62,2095,2096,2117,2133,2149],{},[49,2097,2098,2102,2105],{},[67,2099,2100],{},[70,2101,72],{},[67,2103,2104],{},"nur Convex-Funktionen",[67,2106,2107,963,2109,963,2111,963,2113,963,2115],{},[38,2108,1262],{},[38,2110,1522],{},[38,2112,1359],{},[38,2114,1375],{},[38,2116,1658],{},[49,2118,2119,2123,2126],{},[67,2120,2121],{},[70,2122,135],{},[67,2124,2125],{},"nur der MTA-Prozess",[67,2127,2128,963,2130,2132],{},[38,2129,1297],{},[38,2131,1311],{},", DKIM-Schlüssel",[49,2134,2135,2139,2142],{},[67,2136,2137],{},[70,2138,115],{},[67,2140,2141],{},"nur browsersicher",[67,2143,2144,963,2146,2148],{},[38,2145,1342],{},[38,2147,1325],{}," (keine Secrets)",[49,2150,2151,2155,2158],{},[67,2152,2153],{},[70,2154,244],{},[67,2156,2157],{},"nur aufgabenspezifisch",[67,2159,2160,2163,2164,2166,2167,2170],{},[38,2161,2162],{},"CONVEX_URL"," (API-Endpunkt), ",[38,2165,1280],{}," (zum Ansteuern interner Funktionen), ",[38,2168,2169],{},"LLM_*"," (für den direkten LLM-Zugriff)",[15,2172,2173,2174,2176,2177,2180,2181,2184,2185,2188,2189,2192,2193,2195,2196,2199,2200,1407,2203,2206,2207,2209],{},"Convex-Umgebungsvariablen werden niemals an vom Agenten erzeugten Code weitergereicht. Das Sidecar ",[38,2175,1932],{}," erhält nur die Convex-Client-URL, den Admin-Key des Deployments und die LLM-Konfiguration. Es pollt eine ",[38,2178,2179],{},"internalQuery"," und steuert ",[38,2182,2183],{},"internalMutation","s an, die ein anonymer Client nicht erreichen kann — deshalb authentisiert es sich, genau wie ",[38,2186,2187],{},"apps\u002Fimap"," und ",[38,2190,2191],{},"apps\u002Fmail-sync",", mit dem Admin-Key des Deployments (",[38,2194,1280],{},") über ",[38,2197,2198],{},"setAdminAuth"," am ",[38,2201,2202],{},"ConvexHttpClient",[38,2204,2205],{},"apps\u002Fcode-worker\u002Fsrc\u002FconvexClient.ts","). Compose reicht den Key in den Dienst hinein (",[38,2208,40],{},"). Halten Sie den Worker im internen Docker-Netzwerk und legen Sie seine Endpunkte nicht offen.",[19,2211,2214],{"title":2212,"type":2213},"Bereit zum Deployment?","tip",[15,2215,2216,2217,2221],{},"Eine Schritt-für-Schritt-Einrichtungsanleitung finden Sie im ",[309,2218,2220],{"href":2219},"\u002Fdeveloper\u002Fself-hosting","Self-Hosting-Leitfaden",". Diese Seite behandelt die Architektur und die Entwurfsphilosophie.",[27,2223,2225],{"id":2224},"erste-schritte","Erste Schritte",[389,2227,2231],{"className":2228,"code":2229,"language":2230,"meta":394,"style":394},"language-bash shiki shiki-themes github-light github-dark-dimmed","# 1. Clone the repository\ngit clone https:\u002F\u002Fgithub.com\u002Fwolvesdotink\u002Fowlat.git\ncd owlat\n\n# 2. Configure secrets\ncp .env.selfhost.example .env   # then fill in INSTANCE_SECRET, MTA_*, URLs, ...\n\n# 3. Start the stack\ndocker compose up -d\n\n# 4. Generate the admin key and put it in .env as CONVEX_ADMIN_KEY\ndocker compose exec convex .\u002Fgenerate_admin_key.sh\n\n# 5. Deploy the tenant functions (one-shot job, reads CONVEX_ADMIN_KEY)\ndocker compose --profile deploy run --rm convex-deploy\n\n# 6. Open the dashboard\nopen http:\u002F\u002Flocalhost:3000\n","bash",[38,2232,2233,2238,2250,2258,2262,2267,2281,2285,2290,2304,2308,2313,2328,2332,2337,2358,2362,2367],{"__ignoreMap":394},[398,2234,2235],{"class":400,"line":401},[398,2236,2237],{"class":415},"# 1. Clone the repository\n",[398,2239,2240,2244,2247],{"class":400,"line":412},[398,2241,2243],{"class":2242},"sOLd2","git",[398,2245,2246],{"class":436}," clone",[398,2248,2249],{"class":436}," https:\u002F\u002Fgithub.com\u002Fwolvesdotink\u002Fowlat.git\n",[398,2251,2252,2255],{"class":400,"line":419},[398,2253,2254],{"class":1087},"cd",[398,2256,2257],{"class":436}," owlat\n",[398,2259,2260],{"class":400,"line":427},[398,2261,532],{"emptyLinePlaceholder":531},[398,2263,2264],{"class":400,"line":440},[398,2265,2266],{"class":415},"# 2. Configure secrets\n",[398,2268,2269,2272,2275,2278],{"class":400,"line":448},[398,2270,2271],{"class":2242},"cp",[398,2273,2274],{"class":436}," .env.selfhost.example",[398,2276,2277],{"class":436}," .env",[398,2279,2280],{"class":415},"   # then fill in INSTANCE_SECRET, MTA_*, URLs, ...\n",[398,2282,2283],{"class":400,"line":460},[398,2284,532],{"emptyLinePlaceholder":531},[398,2286,2287],{"class":400,"line":471},[398,2288,2289],{"class":415},"# 3. Start the stack\n",[398,2291,2292,2295,2298,2301],{"class":400,"line":479},[398,2293,2294],{"class":2242},"docker",[398,2296,2297],{"class":436}," compose",[398,2299,2300],{"class":436}," up",[398,2302,2303],{"class":1087}," -d\n",[398,2305,2306],{"class":400,"line":487},[398,2307,532],{"emptyLinePlaceholder":531},[398,2309,2310],{"class":400,"line":495},[398,2311,2312],{"class":415},"# 4. Generate the admin key and put it in .env as CONVEX_ADMIN_KEY\n",[398,2314,2315,2317,2319,2322,2325],{"class":400,"line":506},[398,2316,2294],{"class":2242},[398,2318,2297],{"class":436},[398,2320,2321],{"class":436}," exec",[398,2323,2324],{"class":436}," convex",[398,2326,2327],{"class":436}," .\u002Fgenerate_admin_key.sh\n",[398,2329,2330],{"class":400,"line":517},[398,2331,532],{"emptyLinePlaceholder":531},[398,2333,2334],{"class":400,"line":528},[398,2335,2336],{"class":415},"# 5. Deploy the tenant functions (one-shot job, reads CONVEX_ADMIN_KEY)\n",[398,2338,2339,2341,2343,2346,2349,2352,2355],{"class":400,"line":535},[398,2340,2294],{"class":2242},[398,2342,2297],{"class":436},[398,2344,2345],{"class":1087}," --profile",[398,2347,2348],{"class":436}," deploy",[398,2350,2351],{"class":436}," run",[398,2353,2354],{"class":1087}," --rm",[398,2356,2357],{"class":436}," convex-deploy\n",[398,2359,2360],{"class":400,"line":541},[398,2361,532],{"emptyLinePlaceholder":531},[398,2363,2364],{"class":400,"line":549},[398,2365,2366],{"class":415},"# 6. Open the dashboard\n",[398,2368,2369,2372],{"class":400,"line":559},[398,2370,2371],{"class":2242},"open",[398,2373,2374],{"class":436}," http:\u002F\u002Flocalhost:3000\n",[19,2376,2378],{"title":2377,"type":2213},"Einrichtung mit einem Befehl",[15,2379,2380,2381,2383,2384,2386,2387,2188,2391,1088],{},"Der Befehl ",[38,2382,1233],{}," im Setup-CLI führt diesen gesamten Ablauf für Sie aus — er erzeugt den Admin-Key am Backend und setzt die Convex-Funktions-Umgebungsvariablen (",[38,2385,1229],{},") automatisch. Siehe ",[309,2388,2390],{"href":2389},"\u002Fdeveloper\u002Fsetup-cli","Setup-CLI",[309,2392,2220],{"href":2219},[2394,2395,2396],"style",{},"html pre.shiki code .sIAta, html code.shiki .sIAta{--shiki-default:#22863A;--shiki-dark:#8DDB8C}html pre.shiki code .sYgZi, html code.shiki .sYgZi{--shiki-default:#24292E;--shiki-dark:#ADBAC7}html pre.shiki code .sDN9O, html code.shiki .sDN9O{--shiki-default:#6A737D;--shiki-dark:#768390}html pre.shiki code .s-HuK, html code.shiki .s-HuK{--shiki-default:#032F62;--shiki-dark:#96D0FF}html pre.shiki code .sviXB, html code.shiki .sviXB{--shiki-default:#005CC5;--shiki-dark:#6CB6FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOLd2, html code.shiki .sOLd2{--shiki-default:#6F42C1;--shiki-dark:#F69D50}",{"title":394,"searchDepth":412,"depth":412,"links":2398},[2399,2402,2403,2408,2409,2414],{"id":29,"depth":412,"text":30,"children":2400},[2401],{"id":317,"depth":419,"text":318},{"id":380,"depth":412,"text":381},{"id":1206,"depth":412,"text":1207,"children":2404},[2405,2406,2407],{"id":1237,"depth":419,"text":1238},{"id":1436,"depth":419,"text":1437},{"id":1560,"depth":419,"text":1561},{"id":1681,"depth":412,"text":1682},{"id":1787,"depth":412,"text":1788,"children":2410},[2411,2412,2413],{"id":1794,"depth":419,"text":1795},{"id":1903,"depth":419,"text":1904},{"id":2074,"depth":419,"text":2075},{"id":2224,"depth":412,"text":2225},"Wie Owlat als vollständig selbst gehosteter Stack über Docker Compose läuft — quelloffenes Convex-Backend, eigener MTA und ein austauschbarer LLM-Anbieter.","md",{},"\u002Fvision\u002Fself-hosting",{"title":6,"description":2415},"5.vision\u002F1.self-hosting","bXPG7Nvyc4whaAjaV1kS61jG-h-10oTqzMRJiBFjoYY",[2423,2426],{"title":2424,"path":1688,"stem":2425,"children":-1},"Vision","5.vision\u002F0.index",{"title":2427,"path":2428,"stem":2429,"children":-1},"Agenten-Strecke","\u002Fvision\u002Fagent-pipeline","5.vision\u002F2.agent-pipeline",1786915111065]