Config Inspector (סקירת משתני סביבה)

מה זה Config Inspector?

Config Inspector הוא כלי אדמין שמציג תמונת מצב של הקונפיגורציה: אילו משתני סביבה מוגדרים, מה הערך הפעיל שלהם, מה ברירת המחדל בקוד, והאם הערך שונה מברירת המחדל.

הכלי נותן מענה לשאלות שקשה לענות עליהן מול Render Dashboard:

  • האם המשתנה הזה בכלל מוגדר, או שאנחנו רצים על ברירת מחדל?

  • מה ברירת המחדל שבקוד, ובמה הערך שברנדר שונה ממנה?

  • לאיזה שירות שייך המשתנה, ואיפה צריך להגדיר אותו?

  • אילו משתנים הכרחיים חסרים?

איך נכנסים?

דרך ה-UI: דף Settings ← קטגוריית כלי אדמיןConfig Inspector

ישירות:

GET /admin/config-inspector

הערה

הדף זמין רק לאדמינים (@admin_required ב-webapp/app.py). ערך מוצג ממוסך (********) רק אם הוא מסווג כרגיש — לפי שם המשתנה או לפי סימון מפורש sensitive=True; כל שאר הערכים מוצגים כמות שהם. הסיווג מפורט ב- מיסוך ערכים רגישים, וחשוב לקרוא אותו לפני הוספת משתנה שהוא סוד.

שני עמודים — ולמה

הדף מחולק לשני טאבים, וההפרדה ביניהם אינה קוסמטית אלא נובעת ממגבלה אמיתית.

עמוד 1: שירות ה-Webapp (עם Status וערך פעיל)

העמוד הראשון מציג רק משתנים ששייכים (גם) לשירות ה-Webapp, ורק הם מקבלים עמודות Status ו-Active Value.

הסיבה: ה-Config Inspector הוא קוד שרץ בתוך תהליך ה-Webapp. כשהוא קורא os.environ הוא רואה אך ורק את משתני הסביבה של אותו תהליך. הבוט, שרת ה-MCP וה-webserver הם שירותי Render נפרדים, כל אחד עם ENV משלו — ותהליך ה-Webapp פשוט אינו יכול לראות אותם.

לכן, אילו היינו מציגים Status למשתנה של הבוט, הוא היה מוצג כ-Default או Missing גם כשהוא מוגדר מצוין בשירות הבוט. מידע שגוי גרוע מהיעדר מידע — ולכן העמוד הראשון מסונן, והסינון נאכף בקוד:

# services/config_inspector_service.py — get_config_overview
for definition in self.CONFIG_DEFINITIONS.values():
    # עמוד ראשי: רק משתנים ששייכים (גם) לשירות ה-webapp
    if "webapp" not in definition.services:
        continue

בראש העמוד מוצגים כרטיסי סיכום (סה“כ / שונו מדיפולט / הוגדרו בסביבה / חסרים / ברירת מחדל), שורת סינון (קטגוריה + סטטוס), פירוט לפי קטגוריות, וטבלת המשתנים המלאה. כפתור ”העתק הכל“ מייצא את השורות המוצגות בפורמט KEY=VALUE (שורות .env).

עמוד 2: שירותים אחרים — Bot / MCP / Webserver

העמוד השני מציג את כל המשתנים שיש להם שירות שאינו webapp — כולל משתנים משותפים שמופיעים גם בעמוד הראשון.

מה מוצג: Key / שירות / Default Value / תיאור.

מה לא מוצג, ולמה: אין כאן ``Status`` ואין ``Active Value`` — מאותה סיבה בדיוק שתוארה למעלה. הערכים חיים בתהליכים אחרים ואינם נגישים מכאן. לערכים בפועל יש לבדוק ב-Render Dashboard של השירות הרלוונטי.

  • שורה של משתנה שמוגדר גם בוובאפ מסומנת בתגית ”גם Webapp“ — הערך שלה מופיע בעמוד הראשון.

  • שורת הסינון בעמוד זה היא לפי קטגוריה ולפי שירות (ולא לפי סטטוס — אין כאן סטטוס).

  • ”העתק הכל“ בעמוד זה מעתיק KEY=<ברירת מחדל> ומציין זאת מפורשות, כדי שלא ייווצר רושם שאלו הערכים החיים.

ארבעת הסטטוסים — ואיך נמנעים מ-”Modified“ שגוי

סטטוס

מתי מתקבל

משמעות

Default

אין ערך בסביבה ויש ברירת מחדל בקוד — או שהערך בסביבה זהה לברירת המחדל

רצים על ברירת המחדל. אין מה לעשות

Set

יש ערך בסביבה ו**אין ברירת מחדל בקוד**

המשתנה הוגדר (למשל ברנדר). זו אינה סטייה — אין דיפולט שממנו אפשר לסטות

Modified

יש ערך בסביבה, יש ברירת מחדל, והם שונים

סטייה אמיתית מברירת המחדל — כאן צריך להסתכל

Missing

אין ערך בסביבה, אין ברירת מחדל, והמשתנה מסומן required=True

משתנה הכרחי חסר. מוצג גם כאזהרה בראש הדף

הלוגיקה ממומשת ב-ConfigService.determine_status.

אזהרה

הכלל השורשי: ה-default שרשום ב-ConfigDefinition חייב להיות זהה תו-בתו לברירת המחדל האמיתית בקוד. אחרת מתקבל סטטוס Modified שקרי — משתנה שמוגדר נכון ייראה כאילו מישהו שינה אותו, ואי אפשר יהיה לסמוך על העמודה הזו.

שתי הטעויות שמייצרות Modified שגוי:

1. דיפולט משוער במקום זה שבקוד. אם הקוד עושה os.getenv("X", "60") אבל בהגדרה נרשם default="30" — משתנה שמוגדר ל-60 ברנדר יוצג כ-Modified, למרות שהוא זהה לברירת המחדל בפועל.

2. המצאת דיפולט למשתנה שאין לו דיפולט בקוד. אם בקוד יש os.getenv("TOKEN") בלי ערך שני, אין ברירת מחדל — וההגדרה צריכה להשאיר default ריק. אז הסטטוס יהיה Set (נכון), ולא Modified (מטעה).

הבדיקה לפני הוספה — לאתר את ברירת המחדל האמיתית ולהעתיק אותה כמות שהיא:

rg -n -w 'MY_VAR' -t py -g '!tests/**'

-w מחפש את שם המשתנה כמילה שלמה, ולכן תופס גם os.getenv("MY_VAR"), גם os.getenv('MY_VAR') וגם config.MY_VAR, בלי להיתפס ל-MY_VAR_OTHER.

לאיזה שירות שייך המשתנה? — לבדוק, לא לנחש

השדה services בהגדרה קובע באיזה עמוד המשתנה יופיע. שיוך שגוי מייצר רעש: משתנה של הוובאפ בלבד שמסומן כשייך לכולם מופיע בעמוד 2 כאילו צריך להגדיר אותו בארבעה מקומות.

חשוב

אל תסיקו את השיוך משם המשתנה. שם שנשמע גלובלי (PUSH_*, UPTIME_*, MAINTENANCE_*) לא אומר שהמשתנה נצרך בכל השירותים. בסבב ניקוי אחד הוסרו 68 שיוכים שגויים שנקבעו לפי תחושה.

הנוהל (שלושה צעדים):

  1. למצוא איפה המשתנה נצרך בפועל — חיפוש אחד תופס גם os.getenv (בכל סוג גרשיים) וגם גישה דרך אובייקט הקונפיג (config.MY_VAR):

    rg -n -w 'MY_VAR' -t py -g '!tests/**'
    
  2. לזהות לאיזה שירות שייך הקובץ שנמצא — לפי נקודות הכניסה:

    קובץ / תיקייה

    שירות

    נקודת כניסה

    main.py, handlers/, *_handler.py

    bot

    main.py

    webapp/

    webapp

    webapp/app.py

    mcp_server/

    mcp

    mcp_server/app.py

    services/webserver.py

    webserver

    services/webserver.py

    scripts/

    scripts

    סקריפטים ידניים/CI

  3. קובץ משותף — לפי שרשרת ה-imports. קובץ כמו database/, services/ או utils.py אינו שייך לשירות מסוים בפני עצמו: הוא שייך לשירות רק אם הוא נטען בשרשרת ה-imports של אותה נקודת כניסה. משתנה שנצרך רק ב-webapp/app.py אינו שייך ל-bot/mcp/webserver, נקודה.

טיפ

PORT הוא חריג מוצדק: הוא לא בהכרח מופיע בקוד של השירות, אבל כל שירות web צריך אותו בפקודת ההרצה. חריגים כאלה — לתעד בהערה ליד ההגדרה.

משתנה שמשרת כמה שירותים — לתעד בכל המקומות

משתנה יכול להיות מוגדר בכמה שירותי Render במקביל (למשל MONGODB_URL). במקרה כזה מתעדים אותו בכל המקומות הרלוונטיים, כל אחד במקום שלו — ולא בוחרים ”בית“ אחד:

  1. ``services`` בהגדרה — כל השירותים, לא רק העיקרי:

    "MONGODB_URL": ConfigDefinition(
        key="MONGODB_URL",
        services=("webapp", "bot", "mcp", "webserver"),
        ...
    )
    
  2. ``docs/environment-variables.rst`` — עמודת ”רכיב“ בטבלה המרכזית חייבת לשקף את אותה רשימת שירותים. השורה הזו היא הרפרנס לאנשי DevOps.

  3. תיאור תואם בשני המקומות — ה-description שב-ConfigDefinition (מה שמוצג בטבלת ה-Config Inspector) וההסבר שב-environment-variables.rst צריכים לומר את אותו דבר. שני התיאורים נקראים זה לצד זה כשמדבגים תקלת קונפיגורציה, ותיאורים סותרים גרועים מהיעדר תיאור.

מיסוך ערכים רגישים

ערך ממוסך (********) בשני מקרים:

  • לפי שם המשתנהSENSITIVE_PATTERNS (TOKEN, KEY, PASSWORD, SECRET, URI, CREDENTIALS, AUTH, PRIVATE, CERT, DSN, CONNECTION_STRING).

  • לפי סימון מפורשsensitive=True בהגדרה.

הערה

URL הוסר מהרשימה בכוונה: כתובת ציבורית (WEBAPP_URL, MCP_SERVER_URL) אינה סוד, ומיסוכה הפך את הדף לחסר תועלת. כתובת שמכילה credentials — למשל MONGODB_URL בפורמט scheme://user:pass@host — מסומנת sensitive=True מפורשות, ובנוסף קיים זיהוי אוטומטי של תבנית ה-credentials בתוך URL.

המסקנה המעשית: משתנה חדש שהוא סוד ושמו אינו מכיל אחת מהמילים ברשימה — סמנו ``sensitive=True`` ידנית.

צ’קליסט: הוספת משתנה סביבה חדש

  1. לאתר את הצריכה בפועלgrep על שם המשתנה (לאיזה שירות שייך המשתנה? — לבדוק, לא לנחש).

  2. להעתיק את ברירת המחדל מהקוד תו-בתו — ואם אין דיפולט, להשאיר ריק כדי לקבל Set ולא Modified (ארבעת הסטטוסים — ואיך נמנעים מ-”Modified“ שגוי).

  3. לקבוע ``services`` לפי הצריכה שנמצאה — כל השירותים שצורכים, ורק הם.

  4. ``sensitive=True`` אם זה סוד ששמו לא נתפס אוטומטית (מיסוך ערכים רגישים).

  5. לעדכן את ``docs/environment-variables.rst`` — שורה בטבלה המרכזית עם עמודת ”רכיב“ תואמת ותיאור זהה. זו חובה לפי ההנחיה שבראש אותו עמוד.

  6. לוודא בדף שהמשתנה מופיע בעמוד הנכון ושהסטטוס הגיוני (משתנה שלא נגעתם בו ברנדר אמור להיות Default, לא Modified).

טסטים: tests/test_config_inspector_service.py.

ראו גם