अध्याय 13b क्लाइंट, ग्रुपवेयर & CLI एडमिन: #pConfigFile

कॉन्फ़िगरेशन एज़ कोड: export, plan, apply और Git पाइपलाइन

Git में YAML फ़ाइलों के रूप में पूरी सर्वर स्थिति: टेनेंट, डोमेन (DKIM सेलेक्टर, MTA-STS, रूटिंग), उपनाम, अग्रेषण और स्वचालित उत्तर सहित मेलबॉक्स, वितरण सूचियाँ, साझा मेलबॉक्स, कक्ष, GAL और संसाधन नियम, साझाकरण और प्रत्यायोजन, SMTP, स्मार्ट होस्ट, रूट, ट्रांसपोर्ट नियम, स्पैम और वायरस सुरक्षा, फ़ायरवॉल और ऑटो-बैन, TLS, पहचान स्रोत, API कुंजियाँ (केवल मेटाडेटा), वेबहुक, सूचनाएँ, ऑप्स अलर्ट, POP3, फ़ाइलें, बुकिंग, आर्काइव और DNS फेलओवर। nextxch-cli config plan, terraform plan की तरह दिखाता है कि क्या बनाया, बदला और हटाया जाएगा; apply पहले सब कुछ जाँचता है और फिर उसे एक निश्चित क्रम में लागू करता है। एडमिन: सिस्टम > फ़ाइल के रूप में कॉन्फ़िगरेशन।

संचालन सुझाव और आर्किटेक्चर नोट

सीक्रेट कभी फ़ाइल में नहीं जाते और कभी निर्यात नहीं होते: पासवर्ड, टोकन और कुंजियाँ secretRef: {env: NAME} या secretRef: {file: path} के रूप में लिखी जाती हैं और apply के समय रिज़ॉल्व होती हैं (CI रनर पर nextxch-cli द्वारा; सर्वर पर केवल NEXTXCH_SECRET_* वेरिएबल और <data>/secrets/ के नीचे की फ़ाइलें)। DKIM कुंजियाँ, प्रमाणपत्र और पासवर्ड हैश सर्वर पर ही रहते हैं। API v1 की तरह Business सुविधा; 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 के साथ वर्कफ़्लो

पहले निर्यात और कमिट करें, फिर पुल रिक्वेस्ट के माध्यम से बदलें। 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: पुल रिक्वेस्ट पर plan, मर्ज पर apply

comment चरण सहित पूरी फ़ाइल: रिपॉज़िटरी में deploy/gitops/github-actions.yml। API कुंजी रिपॉज़िटरी secrets से आती है, कभी फ़ाइल से नहीं।

संदर्भ / कॉन्फ़िगरेशन
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: merge request नोट के रूप में plan, डिफ़ॉल्ट ब्रांच पर apply

पूरी फ़ाइल: 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 (Red Hat "YAML" एक्सटेंशन) और JetBrains IDE टाइप करते समय फ़ाइलों की जाँच करते हैं। हर सर्वर /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

पाइपलाइन से ऑथ हेडर और HMAC सीक्रेट के साथ MTA हुक, webhook सीक्रेट के साथ अलर्ट लक्ष्य और DNS-01 के साथ Let's Encrypt पर स्विच: एक 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] सर्वर की स्थिति को निर्धारक (deterministic) रूप से निर्यात करता है: निश्चित दस्तावेज़ क्रम, क्रमबद्ध सूचियाँ, कोई टाइमस्टैम्प नहीं। दो बार निर्यात करने पर वही बाइट्स मिलते हैं, Git diff केवल वास्तविक बदलाव दिखाते हैं। alle Bereiche रिपॉज़िटरी के लिए --split का उपयोग करें (प्रति क्षेत्र एक फ़ाइल: domains.yaml, accounts.yaml ...); छोटे पुल रिक्वेस्ट पढ़ने में आसान रहते हैं।
config plan -f datei|ordner [--prune] प्रति ऑब्जेक्ट दिखाता है: + create, ~ update (field: old -> new) और - delete। एग्ज़िट कोड 0 = कोई परिवर्तन नहीं, 2 = परिवर्तन, 1 = त्रुटि। सीक्रेट केवल "(secret changed)" के रूप में दिखते हैं। ohne Löschen इसे पुल रिक्वेस्ट पर चलाएँ और टिप्पणी के रूप में पोस्ट करें (उदाहरण नीचे)।
config apply -f ... [--prune] [--yes] [--force] [--dry-run] पहले सब कुछ जाँचता है (फ़ील्ड, संदर्भ, सीक्रेट, सुरक्षा नियम), फिर एक निश्चित क्रम में बनाता और अपडेट करता है (टेनेंट, डोमेन, खाते, सूचियाँ, नियम, साझाकरण, ट्रांसपोर्ट ...), और उल्टे क्रम में हटाता है। यदि कोई चरण विफल होता है, तो पहले से पूरे हो चुके चरण पूर्ववत कर दिए जाते हैं। हर परिवर्तन ऑडिट लॉग में दर्ज होता है। fragt nach पाइपलाइन में हमेशा --yes दें; अन्यथा टर्मिनल के बिना apply मना कर देता है। --dry-run, plan के बराबर है।
--prune und Manifest हटाना केवल --prune के साथ और केवल उन क्षेत्रों में होता है जिनका नाम Manifest दस्तावेज़ देता है (Manifest के बिना: केवल फ़ाइल में मौजूद प्रकार)। सर्वर सेटिंग्स कभी नहीं हटाई जातीं। aus Export, 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 (निःशुल्क)। कुंजियाँ: टेनेंट इंटीग्रेशन कुंजियाँ; ये केवल तब तक काम करती हैं जब तक उनका निर्माता एडमिनिस्ट्रेटर है। सर्वर एडमिन की कुंजियाँ पूरा सर्वर देखती हैं, टेनेंट एडमिन की कुंजियाँ केवल वह टेनेंट। export और plan के लिए config.read पर्याप्त है, apply के लिए config.write चाहिए। - दो कुंजियाँ: पुल रिक्वेस्ट में प्लान के लिए config.read, config.write केवल main पर apply जॉब के लिए, संभव हो तो रनर 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 (webhook लक्ष्य का HMAC)। प्लान प्रविष्टि को उसकी id या नाम से दर्शाता है, उदाहरण के लिए hooks[spamcheck].authHeaderValue; सूची में सादा मान अस्वीकार किया जाता है। - बिना id वाली प्रविष्टियों को पहले apply पर एक id मिलती है; एक्सपोर्ट के बाद id को फ़ाइल में रखें, तब सूची का क्रम बदलने पर भी सीक्रेट सही प्रविष्टि के साथ रहते हैं।
ACME zur Laufzeit TlsSettings.acme (enabled, directoryUrl, contactEmail, challenge Http01/TlsAlpn01/Dns01, renewBeforeExpiryDays, reusePrivateKeyForDane) बिना रीस्टार्ट के लागू होता है: appsettings.json आधार बना रहता है, अंतर <data>/acme-settings.json में रहते हैं और पूरे क्लस्टर पर लागू होते हैं। डायरेक्टरी URL बदलने से अगले ऑर्डर पर नया ACME खाता बनता है। appsettings.json पहले Let's Encrypt की staging डायरेक्टरी से परीक्षण करें, फिर प्रोडक्शन पर स्विच करें।
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 से रद्द करती है। रूम और बुकिंग सेटिंग्स हर क्लस्टर नोड पर समान हैं।