#pConfigFile
الإعداد كشيفرة: التصدير والتخطيط والتطبيق وخط معالجة Git
حالة الخادم بالكامل كملفات YAML في Git: المستأجرون، والنطاقات (محددات DKIM وMTA-STS والتوجيه)، وصناديق البريد مع الأسماء المستعارة وإعادة التوجيه والرد التلقائي، وقوائم التوزيع، وصناديق البريد المشتركة، والغرف، وقائمة العناوين العامة (GAL) وقواعد الموارد، والمشاركات والتفويض، وSMTP، والمضيف الذكي (Smart Host)، والمسارات، وقواعد النقل، والحماية من البريد العشوائي والفيروسات، وجدار الحماية والحظر التلقائي، وTLS، ومصادر الهوية، ومفاتيح API (البيانات الوصفية فقط)، وWebhooks، والإشعارات، وتنبيهات التشغيل، وPOP3، والملفات، والحجز، والأرشيف، وDNS failover. يعرض nextxch-cli config plan، على غرار terraform plan، ما سيُنشأ وما سيُغيَّر وما سيُحذف؛ ويتحقق apply من كل شيء أولًا ثم يطبّقه بترتيب ثابت. في لوحة الإدارة: System > Configuration as a file.
لا تدخل الأسرار إلى الملف أبدًا ولا تُصدَّر أبدًا: تُكتب كلمات المرور والرموز والمفاتيح بالصيغة secretRef: {env: NAME} أو secretRef: {file: path} وتُحلَّل وقت التطبيق (بواسطة nextxch-cli على مشغّل CI؛ وعلى الخادم فقط متغيرات NEXTXCH_SECRET_* والملفات الموجودة تحت <data>/secrets/). وتبقى مفاتيح DKIM والشهادات وتجزئات كلمات المرور على الخادم. ميزة من Business مثل API v1؛ أما JSON Schema فمجاني.
1. تنسيق الملف: apiVersion وkind وmetadata.name وspec
ملف واحد يضم عدة مستندات (مفصولة بـ ---) أو مجلد يضم ملفًا واحدًا لكل مجال (config export --split). تبقى الحقول غير الموجودة في الملف كما هي على الخادم؛ أما القوائم (الأسماء المستعارة، الأعضاء) فتُعامَل ككل.
# yaml-language-server: $schema=https://nextxch.com/schemas/nextxch-config-v1.schema.json
apiVersion: nextxch/v1
kind: Manifest # optional: areas in which --prune may delete
metadata: {name: nextxch}
spec: {areas: [domains, accounts, sharing, transport]}
---
apiVersion: nextxch/v1
kind: Domain
metadata: {name: company.com}
spec:
tenant: default
catchAll: info@company.com
dkim: {mode: dual, rsaSelector: s2026, ed25519Selector: ed2026} # keys stay on the server
mtaSts: {mode: enforce, maxAgeSeconds: 604800, mxHosts: [mail.company.com]}
---
apiVersion: nextxch/v1
kind: Account
metadata: {name: anna.smith@company.com}
spec:
displayName: Anna Smith
role: User # User, TenantAdmin, ServerAdmin, ComplianceAuditor
quotaMb: 10240
aliases: [anna@company.com]
forwarding: {address: anna@private.example, keepCopy: true}
outOfOffice: {enabled: false}
password: {secretRef: {env: ANNA_PASSWORD}} # never the password itself
---
apiVersion: nextxch/v1
kind: Account
metadata: {name: info@company.com}
spec: {type: shared, displayName: Info mailbox} # user, shared, room, equipment
---
apiVersion: nextxch/v1
kind: Share
metadata: {name: "info@company.com/mailbox -> anna.smith@company.com"}
spec: {permissions: [read, write, sendAs]}
---
apiVersion: nextxch/v1
kind: SmtpSettings
metadata: {name: server} # server-wide settings are always named "server"
spec:
outboundSmartHost: smtp.provider.example
outboundSmartPort: 587
smartHostPassword: {secretRef: {file: secrets/smarthost.txt}}
2. سير العمل مع nextxch-cli
صدّروا والتزموا (commit) أولًا، ثم أجروا التغييرات عبر طلبات السحب (pull requests). مثال على مخرجات plan:
export NEXTXCH_URL=https://mail.company.com
export NEXTXCH_API_KEY=smk_... # key with config.read or config.write
nextxch-cli config export --split nextxch/
nextxch-cli config validate -f nextxch/ # offline: kinds and fields
nextxch-cli config plan -f nextxch/
~ Domain company.com
mtaSts.mode: "testing" -> "enforce"
~ Account anna.smith@company.com
quotaMb: 10240 -> 20480
aliases: ["anna@company.com"] -> ["a.smith@company.com","anna@company.com"]
~ password: (secret changed)
+ Account info@company.com
type: "shared"
displayName: "Info mailbox"
Not in the file (1), deleted only with --prune:
Account old@company.com
Plan: 1 to create, 2 to update, 0 to delete, 41 unchanged.
# exit code 2 = changes
nextxch-cli config apply -f nextxch/ --prune --yes
nextxch-cli config history
3. GitHub Actions: الخطة عند طلب الدمج (Pull Request)، والتطبيق عند الدمج
الملف الكامل مع خطوة التعليق: deploy/gitops/github-actions.yml في المستودع. ويأتي مفتاح API من أسرار المستودع، وليس من الملف أبدًا.
name: nextxch config
on:
pull_request: {paths: ["nextxch/**"]}
push: {branches: [main], paths: ["nextxch/**"]}
permissions: {contents: read, pull-requests: write}
concurrency: {group: nextxch-config, cancel-in-progress: false}
env: {NEXTXCH_IMAGE: "nextxch/server:latest"}
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Plan
env:
NEXTXCH_URL: ${{ vars.NEXTXCH_URL }}
NEXTXCH_API_KEY: ${{ secrets.NEXTXCH_CONFIG_READ_KEY }}
run: |
set +e
docker run --rm -e NEXTXCH_URL -e NEXTXCH_API_KEY -e NO_COLOR=1 -v "$PWD:/work" -w /work \
--entrypoint nextxch-cli "$NEXTXCH_IMAGE" config plan -f nextxch/ --prune > plan.txt 2>&1
code=$?; cat plan.txt; [ "$code" -ne 1 ]
# danach: actions/github-script postet plan.txt als Kommentar (siehe deploy/gitops)
apply:
if: github.event_name == 'push'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- env:
NEXTXCH_URL: ${{ vars.NEXTXCH_URL }}
NEXTXCH_API_KEY: ${{ secrets.NEXTXCH_CONFIG_WRITE_KEY }}
SMARTHOST_PASSWORD: ${{ secrets.SMARTHOST_PASSWORD }}
run: |
docker run --rm -e NEXTXCH_URL -e NEXTXCH_API_KEY -e SMARTHOST_PASSWORD -v "$PWD:/work" -w /work \
--entrypoint nextxch-cli "$NEXTXCH_IMAGE" config apply -f nextxch/ --prune --yes
4. GitLab CI: الخطة كملاحظة على طلب الدمج، والتطبيق على الفرع الافتراضي
الملف الكامل: deploy/gitops/gitlab-ci.yml. المفاتيح متغيرات CI/CD مقنّعة ومحمية؛ ويمنع resource_group تنفيذ عمليتَي apply في الوقت نفسه.
stages: [validate, plan, apply]
variables: {CONFIG_DIR: nextxch, NO_COLOR: "1"}
.nextxch-cli:
image: {name: "nextxch/server:latest", entrypoint: [""]}
validate:
extends: .nextxch-cli
stage: validate
script: [nextxch-cli config validate -f "$CONFIG_DIR"]
plan:
extends: .nextxch-cli
stage: plan
rules: [{if: '$CI_PIPELINE_SOURCE == "merge_request_event"'}]
variables: {NEXTXCH_API_KEY: $NEXTXCH_CONFIG_READ_KEY}
script:
- set +e
- nextxch-cli config plan -f "$CONFIG_DIR" --prune > plan.txt 2>&1; code=$?
- cat plan.txt; test "$code" -ne 1
artifacts: {when: always, paths: [plan.txt]}
# plan-comment: curl auf /merge_requests/:iid/notes mit plan.txt (siehe deploy/gitops)
apply:
extends: .nextxch-cli
stage: apply
resource_group: nextxch-config
environment: production
rules: [{if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'}]
variables: {NEXTXCH_API_KEY: $NEXTXCH_CONFIG_WRITE_KEY}
script: [nextxch-cli config apply -f "$CONFIG_DIR" --prune --yes]
5. JSON Schema في المحرر واستدعاء REST
تتحقق VS Code (إضافة "YAML" من Red Hat) وبيئات JetBrains من الملفات أثناء الكتابة. كما يوفر كل خادم المخطط على /api/v1/config/schema.
# .vscode/settings.json
{
"yaml.schemas": {
"https://nextxch.com/schemas/nextxch-config-v1.schema.json": ["nextxch/*.yaml"]
}
}
# Plan ohne CLI, direkt per REST (YAML als Body):
curl -s -X POST "$NEXTXCH_URL/api/v1/config/plan?prune=true" \
-H "Authorization: Bearer $NEXTXCH_API_KEY" \
-H "Content-Type: application/yaml" --data-binary @nextxch.yaml
6. الأسرار في القوائم وACME دون إعادة تشغيل
خطافات MTA مع ترويسة مصادقة وسر HMAC من خط المعالجة، ووجهة تنبيه مع سر webhook، والتحول إلى Let's Encrypt مع DNS-01: عملية apply واحدة، دون إعادة تشغيل.
apiVersion: nextxch/v1
kind: MtaFilters
metadata: {name: server}
spec:
hooks:
- name: spamcheck
url: https://hooks.company.com/check
stages: [data]
authHeaderName: Authorization
authHeaderValue: {secretRef: {env: HOOK_TOKEN}} # in the plan: hooks[spamcheck].authHeaderValue
hmacSecret: {secretRef: {file: secrets/hook-hmac.txt}}
---
apiVersion: nextxch/v1
kind: OpsAlerts
metadata: {name: server}
spec:
targets:
- name: pager
type: webhook
address: https://pager.company.com/hook
secret: {secretRef: {env: PAGER_SECRET}}
---
apiVersion: nextxch/v1
kind: TlsSettings
metadata: {name: server}
spec:
acme:
enabled: true
directoryUrl: https://acme-v02.api.letsencrypt.org/directory
contactEmail: postmaster@company.com
challenge: Dns01
renewBeforeExpiryDays: 30
الإعدادات والقيم الافتراضية وأفضل الممارسات
| الإعداد / الموقع | الوصف | القيمة الافتراضية | أفضل ممارسة للإنتاج |
|---|---|---|---|
config export [--area a,b] [--split ordner/] [-o datei] |
يصدّر حالة الخادم بشكل حتمي: ترتيب ثابت للمستندات، وقوائم مرتبة، ودون طوابع زمنية. ويعطي التصدير مرتين البايتات نفسها، ولا تُظهر فروق Git إلا التغييرات الفعلية. | alle Bereiche |
استخدموا --split للمستودعات (ملف واحد لكل مجال: domains.yaml وaccounts.yaml ...)؛ فتبقى طلبات الدمج الصغيرة سهلة القراءة. |
config plan -f datei|ordner [--prune] |
يعرض لكل كائن + إنشاء، و~ تحديث (field: old -> new)، و- حذف. رمز الخروج 0 = لا تغييرات، 2 = توجد تغييرات، 1 = خطأ. ولا تظهر الأسرار إلا بصيغة "(secret changed)". | ohne Löschen |
شغّلوه على طلبات الدمج (Pull Requests) وانشروا النتيجة كتعليق (أمثلة أدناه). |
config apply -f ... [--prune] [--yes] [--force] [--dry-run] |
يفحص كل شيء أولًا (الحقول والمراجع والأسرار وقواعد الأمان)، ثم ينشئ ويحدّث بترتيب ثابت (المستأجرون والنطاقات والحسابات والقوائم والقواعد والمشاركات والنقل...)، ويحذف بالترتيب العكسي. وإذا فشلت خطوة، تُلغى الخطوات المنجزة بالفعل. ويُسجَّل كل تغيير في سجل التدقيق. | fragt nach |
مرّروا دائمًا --yes في خطوط المعالجة (Pipelines)؛ وإلا يرفض apply التنفيذ في غياب طرفية. و--dry-run يعادل plan. |
--prune und Manifest |
لا تحدث عمليات الحذف إلا مع --prune، وفقط في المجالات التي يسميها مستند Manifest (دون Manifest: الأنواع الموجودة في الملف فقط). ولا تُحذف إعدادات الخادم أبدًا. | aus |
يكتب التصدير مستند Manifest؛ وإذا كنتم تديرون أجزاءً فقط، فضيّقوا نطاق المجالات. |
Schutz: letzter Admin, Domain mit Postfächern |
تُرفض الخطة التي لا تُبقي أي مسؤول خادم نشط، وكذلك حذف نطاق لا يزال يحتوي على صناديق بريد. ويتجاوز --force ذلك (يُعطَّل النطاق حينئذٍ بدلًا من حذفه). ولا يستطيع مفتاح API إلغاء نفسه. | aktiv |
لا تكتبوا --force بشكل ثابت في خط معالجة أبدًا. |
secretRef: {env: NAME} | {file: pfad} |
مرجع إلى سر بدلًا من قيمته. يحلّه nextxch-cli على المشغّل (أي متغير، أو ملف نسبةً إلى ملف التكوين). ولا يقرأ الخادم نفسه إلا NEXTXCH_SECRET_* والملفات الموجودة تحت <data>/secrets/. ولا تُعد إعادة تطبيق القيمة نفسها تغييرًا. | - |
مرّروا الأسرار من مخزن أسرار GitHub/GitLab إلى المهمة كمتغيرات بيئة. |
Scopes config.read / config.write |
REST: GET /api/v1/config/export، POST /api/v1/config/plan، POST /api/v1/config/apply، GET /api/v1/config/history، GET /api/v1/config/schema (مجاني). المفاتيح: مفاتيح تكامل المستأجر؛ تعمل فقط ما دام منشئها لا يزال مسؤولًا. ترى مفاتيح مسؤول الخادم الخادم بأكمله، ومفاتيح مسؤول المستأجر ذلك المستأجر فقط. يكفي config.read للتصدير والتخطيط، ويتطلب apply الصلاحية config.write. | - |
مفتاحان: config.read للخطط في طلبات الدمج (Pull Requests)، وconfig.write فقط لمهمة التطبيق على main، مع تقييده بعناوين IP الخاصة بالمشغّلات إن أمكن. |
Mandanten-Admins |
يقوم مسؤول المستأجر (عبر الجلسة أو أحد مفاتيحه) بتصدير كائنات مستأجره فقط والتخطيط لها وتطبيقها: Domain (تعديل فقط، دون إنشاء أو حذف)، وAccount، وDistributionList، وShare، وGalVisibilityRule، وResourceAccessRule، وWebhook. تبقى إعدادات الخادم والمستأجرون ومصادر الهوية ومفاتيح API لدى مسؤول الخادم؛ والحسابات ذات الدور ServerAdmin أو ComplianceAuditor خارج نطاقه. يحذف --prune داخل المستأجر نفسه فقط، ويعرض السجل عمليات التطبيق الخاصة به فقط. | - |
للموزعين والأقسام: يحتفظ كل مستأجر بمستودعه الخاص ومفتاحه الخاص. يمكن حذف الحقل "tenant"؛ إذ يُملأ بالمستأجر نفسه. |
secretRef in Listen |
تتلقى عناصر القوائم الأسرار أيضًا عبر secretRef: MtaFilters hooks[].authHeaderValue وhooks[].hmacSecret، وOpsAlerts targets[].secret (HMAC لوجهة webhook). تسمي الخطة العنصر بمعرّفه أو اسمه، مثل hooks[spamcheck].authHeaderValue؛ وتُرفض القيمة الصريحة في القائمة. | - |
تحصل العناصر التي لا تملك معرّفًا على معرّف عند أول apply؛ بعد التصدير احتفظ بالمعرّفات في الملف، فتبقى الأسرار مع العنصر الصحيح عند إعادة ترتيب القائمة. |
ACME zur Laufzeit |
يُطبَّق TlsSettings.acme (enabled وdirectoryUrl وcontactEmail وchallenge Http01/TlsAlpn01/Dns01 وrenewBeforeExpiryDays وreusePrivateKeyForDane) دون إعادة تشغيل: يبقى appsettings.json هو الأساس، وتُحفظ الفروقات في <data>/acme-settings.json وتسري على العنقود بأكمله. يؤدي تغيير عنوان URL للدليل إلى إنشاء حساب ACME جديد عند الطلب التالي. | appsettings.json |
اختبر أولًا باستخدام دليل staging الخاص بـ Let's Encrypt، ثم انتقل إلى الإنتاج. |
Arten (kinds) |
Tenant وDomain وAccount وDistributionList وGalVisibilityRule وResourceAccessRule وShare وSmtpSettings وRoute وTransportRules وSpamFilter وMtaFilters وFirewallRule وTlsSettings وIdentitySource وApiKey وWebhook وNotifications وOpsAlerts وDnsFailover وPop3 وFileStorage وBooking وArchivePolicy. جميع الحقول: JSON Schema. | - |
لا تُنشأ مفاتيح API من الملفات (سرها موجود مرة واحدة فقط)؛ يغيّر الملف التسمية والنطاقات ويلغي المفاتيح باستخدام --prune. إعدادات القاعات والحجوزات متطابقة على كل عقدة في العنقود. |