نشر خاص بعيد
يشغّل هذا النمط Libre WebUI وOllama وCloudflare Tunnel على مضيف Docker واحد من دون نشر منافذ التطبيق أو Ollama. يشكّل Cloudflare Access حد الهوية الخارجي؛ وتبقى مصادقة Libre WebUI الحد الداخلي. أما Work وWatchtower فخياران مستقلان يعادلان صلاحيات root.
هذا القالب هو بنية solo ذات نسخة واحدة: تتشارك SQLite والـblobs المحلية المشفّرة
والمتجهات المضمّنة والتنسيق المحلي وعامل المهام المضمّن وحدة بيانات التطبيق. لا
تحوّله إلى نشر فريق بتغيير محددات الواجهة الخلفية في .env. يجب أن تستخدم عمليات
نشر الفريق ملف المستودع docker-compose.team.yml (وdocker-compose.team.work.yml
عند تفعيل Work)، الذي يجهّز PostgreSQL/PGVector وتخزين S3 ذا إصدارات وRedis وعاملًا
خارجيًا والبوابة كبنية واحدة منسقة.
استخدم
deploy/private/docker-compose.yml
كنقطة بداية. وهو يستخدم صورة main افتراضيًا:
LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui:main
تصلح علامة dev لمثيل تطوير جرى الاشتراك فيه صراحة، لا للإعداد الافتراضي للعميل.
نموذج الأمان
- يحمي Cloudflare Access اسم المضيف كاملًا، بما في ذلك
/api/*وترقيات WebSocket. لا تضف مسارات تجاوز عامة. - يتطلب Libre WebUI حسابًا حاليًا لواجهات التطبيق. وتتطلب عمليات دورة حياة النماذج وWork أن يكون الدور الحالي في قاعدة البيانات مسؤولًا.
- يستخدم التطبيق وOllama وSearXNG وcloudflared شبكة Compose خاصة فقط. ولا ينشر المضيف أي منافذ للتطبيق.
- تشغّل خدمة SearXNG المضمّنة البحث على الويب الاختياري. وهي داخلية
فقط وخاملة حتى يفعّل مسؤول البحث من الإعدادات > البحث؛ عيّن
SEARXNG_SECRETفي.envقبل تشغيل الحزمة. - يعمل التطبيق من دون root، وبنظام ملفات جذري للقراءة فقط، وبلا قدرات Linux، ومع no-new-privileges وحدود CPU والذاكرة وPID.
- يبقى Work معطّلًا ما لم يُضمّن أحد ملفات التجاوز الخاصة به. وعند تفعيله، تضيف حاوياته نظام ملفات جذريًا للقراءة فقط وإسقاط القدرات وحدود الموارد ووحدة مساحة العمل وسياسة شبكة ترفض افتراضيًا.
لا تركّب الحزمة الأساسية socket Docker. ويحافظ تفعيل Work عبر
docker-compose.work-proxy.yml على ذلك: يحتفظ وكيل socket على شبكة داخلية بالـsocket
ولا يمرر إلا أقسام API التي يستخدمها Work (الحاويات والصور والوحدات والشبكات وexec
والمعلومات)؛ ويرفض الوكيل نقاط swarm والأسرار والبناء والنظام، ولا يحتاج التطبيق
إلى تركيب socket أو عضوية مجموعته. يضيّق الوكيل سطح Docker API، لا نطاق الضرر لما
يمرره؛ فمن يستطيع إنشاء حاويات يستطيع تركيب مسارات المضيف. لذلك اعتبره طبقة تحصين
حقيقية، لا عزلًا متعدد المستأجرين.
تظل بدائل الـsocket الخام أكبر حدود الثقة: تمنح تجاوزات
docker-compose.work.yml وWatchtower عملية داخل حاوية القدرة على إصدار استدعاءات
Docker API اعتباطية والتحكم في المضيف. ولا يجعل تركيب socket للقراءة فقط الوصول
إلى Docker API للقراءة فقط. يرفض مساعد النسخ الاحتياطي المدمج وراثة socket خام؛
رحّل Work إلى الوكيل المفلتر قبل الاعتماد على النسخ المدمجة المجدولة.
الإعداد الأولي
- أنشئ مشغّل sudo بلا root، وتحقق من تسجيل SSH بالمفتاح قبل تعطيل SSH لـroot.
- انسخ
deploy/private/.env.exampleإلى/opt/libre-webui/.env، واضبط الوضع0600، وأنشئ أسرارًا فريدة، وحددBLOB_QUOTA_BYTES_PER_USERبما يناسب المضيف. ينهيBLOB_QUOTA_RESERVATION_TTL_MSحجوزات الرفع المهجورة؛ والافتراضي ساعة واحدة. - إذا كنت ستفعّل Work، عيّن
DOCKER_GIDإلى المجموعة الرقمية المالكة لـ/var/run/docker.sock. - خزّن رمز Cloudflare Tunnel في
/opt/libre-webui/secrets/tunnel-tokenبالوضع0640أو أشد. - أنشئ تطبيق Cloudflare Access مستضافًا ذاتيًا لاسم المضيف كاملًا، واستخدم جلسة
مدتها 24 ساعة، ولا تسمح إلا للهويات المقصودة. فعّل Protect with Access على
مسار النفق. إذا احتاجت المراقبة فحص صحة عامًا، فأنشئ تطبيقًا أو سياسة مستقلة
محصورة بالمسار
/health/liveوحده. لا تضف أبدًا سياسة Bypass شاملة إلى التطبيق الرئيسي؛ فسياسات Bypass المطابقة تهزم سياسة Allow. - اترك
ENABLE_SIGNUP=false. بعد أن تحمي قائمة Access المسموح بها اسم المضيف، أنشئ أول مسؤول محلي؛ تسمح قاعدة بيانات فارغة تلقائيًا بحساب الإعداد الأول هذا. لا تفعّل التسجيل إلا لنافذة تسجيل لاحقة مقصودة. - اضبط قيود اسم المضيف في Turnstile، وعيّن
TURNSTILE_EXPECTED_HOSTNAMEإلى اسم المضيف العام الدقيق.
ابدأ وتحقق:
cd /opt/libre-webui
docker compose config --quiet
docker compose up -d
docker compose ps
لتفعيل Work، أضف تجاوز وكيل الـsocket عمدًا:
docker compose -f docker-compose.yml -f docker-compose.work-proxy.yml up -d
يبقى بديل الـsocket الخام (docker-compose.work.yml) متاحًا لعمليات النشر التي
تحتاجه، مع تبعات الثقة الموضحة أعلاه.
بعد تفعيل Access، تحتاج اختبارات سطر الأوامر السريعة إلى رمز خدمة Cloudflare Access ما لم يكن للمسار المحدد تجاوز ضيق. خزّن بيانات الاعتماد خارج سجل shell وأرسل الترويستين:
curl --fail --silent --show-error \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
https://your-hostname.example/api/auth/system-info
يجب أن يعيد طلب غير موثّق إلى API تطبيق محمية القيمة 401:
curl --output /dev/null --write-out '%{http_code}\n' \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
https://your-hostname.example/api/work/tasks
تحصين المضيف
يتضمن الدليل جزء إعداد sshd وسجن fail2ban. قبل تطبيق جزء sshd، تحقق في طرفية أخرى
من جلسة sudo مستقلة بلا root. اختبر الإعداد باستخدام sshd -t قبل إعادة تحميل SSH.
استخدم UFW (أو جدارًا ناريًا مكافئًا) لرفض الحركة الواردة افتراضيًا، والسماح فقط بـSSH محدود المعدل. لا ينشر Docker منافذ خدمات في هذا القالب:
ufw default deny incoming
ufw default allow outgoing
ufw limit OpenSSH
ufw enable
أبقِ ترقيات الأمان التلقائية مفعّلة. عطّل تمرير X11 والوكيل وTCP إلا إذا كانت للنشر حاجة موثقة إليها.
النسخ الاحتياطي والاستعادة
قبل أخذ نسخة احتياطية، شغّل جرد الاستعداد للاستعادة للقراءة فقط داخل حاوية النشر العاملة. يستخدم ذلك إصدار التطبيق المنشور وبيئته ووحدة بياناته المركبة بالضبط. قد يفحص أمر من نسخة مصدر على المضيف قاعدة بيانات خاطئة أو يشغّل مصدرًا يختلف عن الصورة المنشورة.
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
تعني حالة الخروج 0 عدم العثور على عوائق للاستعداد للاستعادة، وتعني 1 أن تقرير
JSON يتضمن عوائق، وتعني 2 تعذر تشغيل الأمر. لا يتضمن التقرير إلا بصمة مفتاح
التشفير ومؤشرات وجود الأسرار؛ ولا يطبع مفتاحًا أو قيمة سر أخرى أبدًا. احتفظ بالجرد
مع النسخة المقابلة كي يقارن المشغّلون إصدار التطبيق وبصمة المخطط وموارد Work
المتوقعة والاستثناءات قبل الاستعادة.
أنشئ مفاتيح مخصصة لتشفير النسخ وتوقيعها باستخدام الصورة المنشورة بالضبط. أبقِ هذا الدليل خارج وحدة التطبيق، وانسخ مفتاح التشفير ومفتاح التوقيع الخاص إلى موقع استعادة محمي مستقل:
install -d -m 0700 /etc/libre-webui/backup-keys
image_ref=$(docker inspect libre-webui --format '{{.Image}}')
docker run --rm --user 0:0 --read-only --network none --cap-drop ALL \
--security-opt no-new-privileges \
--mount type=bind,src=/etc/libre-webui/backup-keys,dst=/backup-keys \
--entrypoint /usr/local/bin/libre-webui "$image_ref" \
backup keygen \
--directory /backup-keys
يرفض توليد المفاتيح ملفات الإخراج الموجودة. لا تنشئ مفاتيح جديدة فوق مجموعة نسخ قائمة؛ ففقدان مفتاح تشفير الأرشيف أو هوية التوقيع يجعل دليل الاستعادة المقابل غير قابل للاستخدام.
ثبّت برامج النسخ والاستعادة ووحدات systemd المرفقة، ثم فعّل المؤقت:
install -d -m 0700 /var/backups/libre-webui
install -m 0750 deploy/private/libre-webui-backup \
/usr/local/sbin/libre-webui-backup
install -m 0750 deploy/private/libre-webui-restore \
/usr/local/sbin/libre-webui-restore
install -m 0644 deploy/private/libre-webui-backup.{service,timer} \
/etc/systemd/system/
systemctl daemon-reload
systemctl enable --now libre-webui-backup.timer
تقرأ الوحدة اختياريًا تجاوزات الصيانة فقط من /etc/libre-webui/backup.env؛ ولا
تحمّل ملف .env الخاص بالتطبيق. أنشئ الملف كـroot فقط عند الحاجة إلى تجاوز:
install -d -m 0750 /etc/libre-webui
install -m 0600 /dev/null /etc/libre-webui/backup.env
يمكن تعيين LIBRE_WEBUI_STACK_DIR وLIBRE_WEBUI_BACKUP_RETENTION_DAYS
وLIBRE_WEBUI_CONTAINER_NAME وLIBRE_WEBUI_BACKUP_KEY_DIR فيه مباشرة. أبقِ الملف
مملوكًا لـroot وبالوضع 0600. ويجب أن يظل دليل مفاتيح مخصصًا قابلًا للقراءة من root
داخل بيئة systemd المعزولة.
يغيّر تعديل LIBRE_WEBUI_BACKUP_DIR حدود الكتابة في systemd أيضًا. يجب أن يكون
الدليل موجودًا قبل بدء الخدمة، وتحتاج الوحدة إلى جزء إعداد مطابق. مثلًا، بعد تعيين
LIBRE_WEBUI_BACKUP_DIR=/srv/backups/libre-webui في backup.env:
install -d -m 0700 /srv/backups/libre-webui
systemctl edit libre-webui-backup.service
أضف هذا المسار الدقيق في المحرر، ثم أعد تحميل الوحدة:
[Service]
ReadWritePaths=/srv/backups/libre-webui
systemctl daemon-reload
systemctl start libre-webui-backup.service
من دون إدخال ReadWritePaths= المطابق، يمنع ProtectSystem=strict المؤقت من
الكتابة إلى موقع مخصص كما ينبغي.
تسمح خدمة النسخ الاحتياطي بما يصل إلى ست ساعات للأرشيفات الكبيرة. يحصل المساعد
على قفل المضيف، ولا يوقف التطبيق إلا إذا كان عاملًا بالفعل، وينشئ الأرشيف من وحدة
ساكنة باستخدام الصورة المنشورة نفسها. للأرشيف بيان موقّع وحمولة مشفّرة لدى المشغّل؛
ويشمل دليل البيانات وإعداد وقت التشغيل والأسرار اللازمة لفتح تلك الحالة. ثم يتحقق
المساعد مستقلًا من الأرشيف الكامل قبل نشر تقرير بياناته الوصفية ذريًا. تحصل حاويات
الصيانة للقراءة فقط على tmpfs خاص قابل للكتابة في /tmp لفحص SQLite والتحقق
الموثّق من الأرشيف؛ ولا يستمر نص صريح مؤقت في طبقة الحاوية. انسخ الملفين ومفاتيح
الاستعادة المحمية بصورة مستقلة إلى خارج المضيف.
عندما يستخدم Work الملف docker-compose.work-proxy.yml، يجب أن تثبت الاستعادة أيضًا
وجود كل وحدة Work تشير إليها قاعدة البيانات. يقرأ المساعد DOCKER_HOST للتطبيق
المنشور، ويجد خدمة وكيل الـsocket في مشروع Compose العامل نفسه، ويكتشف شبكتهما
الداخلية المشتركة الوحيدة من اتصالات Docker الفعلية. يضع Compose بادئة اسم المشروع
على الشبكة، فلا تضبط أو تثبّت اسمًا متوقعًا. تنضم حاوية إنشاء الأرشيف وحدها إلى تلك
الشبكة وتصل إلى الوكيل المفلتر؛ ولا تتلقى socket خامًا. ويستمر التحقق المستقل من
الأرشيف مع --network none. يؤدي فقد الوكيل أو نقطة غير متوقعة أو شبكة مشتركة
خارجية أو ملتبسة أو تركيب socket خام إلى الفشل قبل إيقاف التطبيق ونشر الأرشيف.
اختبر الاستعادة إلى وحدة جديدة من دون استبدال الوحدة العاملة:
LIBRE_WEBUI_RESTORE_IMAGE="$image_ref" \
libre-webui-restore \
/var/backups/libre-webui/libre-webui-integrated-YYYYMMDDTHHMMSSZ.lwb \
libre-webui-restore-drill
يرفض مساعد الاستعادة وحدة أو هدف إعداد موجودًا، ويتحقق من الأرشيف وجرد الاستعادة
الداخلي في تخزين مؤقت، ثم ينسخ البيانات إلى الوحدة الجديدة ويكتب runtime.json
وsecrets.json المستعادين بصلاحيات خاصة. ولا يعيد توصيل الحزمة العاملة أو تشغيلها.
افحص الإعداد المستعاد، وحدّث قيم النشر عمدًا، واختبر الوحدة المستعادة بحزمة معزولة.
يمكن سحب نماذج Ollama مجددًا. تقع وحدات Work في Docker وPVCs في Kubernetes ومجلدات Work المرتبطة بالمضيف خارج دليل بيانات التطبيق، وتحتاج إلى لقطات منسقة وسياسة احتفاظ خاصة بها.
التحديثات
Libre WebUI ذو حالة حتى عندما تكون علامة صورته قابلة للتغيير. يضع ملف Compose الأساسي وسمًا دائمًا يستثني التطبيق من Watchtower. لا ترقّه إلا كإجراء منسق للمشغّل:
- سجّل معرّف الصورة العاملة، وحل البديل الذي تمت مراجعته إلى digest ثابت.
- شغّل
libre-webui recovery-check، وابدأ خدمة النسخ، واشترط إنشاء أرشيف جديد وتقرير تحقق قبل المتابعة. - عيّن
LIBRE_WEBUI_IMAGEإلى digest المراجع، واسحبه، وأعد إنشاءlibre-webuiوحده باستخدام Docker Compose. لا تحذف وحدة بياناته أو تعيد إنشاءها. - اشترط نجاح
/health/readyوتسجيل الدخول والجلسة والسجل واسترجاع المستندات واختبارات Work السريعة. ارجع إلى digest الصورة المسجل إذا فشلت؛ واحتفظ بالحالة الفاشلة والنسخة المتحقق منها للتشخيص.
تسلسل المضيف يدوي عمدًا. لا تستبدل digest إلا بعد مراجعته، وافحص أحدث زوج .lwb
و.json قبل السحب:
docker inspect libre-webui --format '{{.Config.Image}} {{.Image}}'
docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data
systemctl start libre-webui-backup.service
systemctl --no-pager --full status libre-webui-backup.service
ls -lt /var/backups/libre-webui/libre-webui-integrated-* | head
# Set LIBRE_WEBUI_IMAGE=ghcr.io/libre-webui/libre-webui@sha256:REVIEWED_DIGEST
# in the root-owned .env, then recreate only the application.
docker compose pull libre-webui
docker compose up -d --no-deps libre-webui
docker inspect libre-webui --format '{{.State.Health.Status}} {{.Image}}'
يبقى تجاوز Watchtower الاختياري الحامل للـsocket متاحًا فقط للحاويات الجانبية المعلّمة صراحة في الملف الأساسي:
docker compose \
-f docker-compose.yml \
-f docker-compose.watchtower.yml \
up -d
يفحص Watchtower كلاً من Ollama وSearXNG كل 30 دقيقة. تبقى بيانات نماذج Ollama في
وحدتها المسماة، وإعداد SearXNG في موضع التركيب المرتبط. ولا يحدّث Libre WebUI أو
cloudflared أو وكيل socket Work أو بيئات Work المعزولة. يتبع نشر العميل main؛
وقد يختار مثيل تجريبي :dev، لكن التطبيق يظل يتطلب الترقية اليدوية نفسها المشروطة
بالنسخ الاحتياطي. لا تربط هذه الحزمة الفردية الخاصة بخدمات استمرارية الفريق؛ بل
انشر بنية الفريق الكاملة.