feat: multi webhook SaaS & secured debug/servers#61
Merged
tqmsh merged 34 commits intoruxailab:setupWizardfrom Feb 20, 2026
Merged
feat: multi webhook SaaS & secured debug/servers#61tqmsh merged 34 commits intoruxailab:setupWizardfrom
tqmsh merged 34 commits intoruxailab:setupWizardfrom
Conversation
- Add finally block to close Discord client even if start() fails (auth.py) - Return empty list instead of None in _get_webhook_urls() to prevent TypeError - Track aiohttp session ownership and cleanup when created outside context manager
- Remove _check_server_configurations() from on_ready (bots shouldn't message on redeploy) - Keep on_guild_join handler for first-time setup guidance (user-initiated action) - Change footer from 'Powered by DisgitBot SaaS' to 'Powered by DisgitBot'
- /set_webhook now requires setup_completed before allowing configuration - /webhook_status shows per-server webhook timestamps instead of org-level - Prevents confusing behavior when old data exists from previous setup
- Add background thread to clean up abandoned OAuth sessions (>10 min) - Extend setup state token expiration from 30min to 7 days for org approval - Disable PR automation at webhook handler level (returns 501)
- Remove /set_webhook command to clean up UI - Keep other CI/CD related commands (add_repo, remove_repo, list_repos) - PR automation code preserved in auth.py for future re-enable
0968749 to
02830f6
Compare
- Add shared.py module for global bot_instance reference - Add notify_setup_complete with asyncio.run_coroutine_threadsafe - Add trigger_initial_sync with GitHub App identity - Add find_installation_id to GitHubAppService for auto-discovery - Integrate setup notification and initial sync into complete_setup flow
- Wrap all synchronous Firestore I/O with asyncio.to_thread in: bot.py (on_guild_join), admin_commands, config_commands, analytics_commands, user_commands, notification_commands - Fix notification_service._get_webhook_urls sync calls in async method - Ensures Discord event loop is never blocked by network I/O - Disabled commands also updated for future maintainability
- Replace threading.Lock (blocked ALL users) with per-user _active_links set - Replace wait_for_username polling (time.sleep loop tying up threads for up to 5 min) with asyncio.Event driven by Flask OAuth callback - Flask callback uses loop.call_soon_threadsafe(event.set) for instant thread-safe notification — zero threads consumed while waiting - Delete dead wait_for_username function - Handle timeout explicitly with oauth_sessions cleanup
Flask session signing key must be set in production to prevent cookie forgery attacks (verified via flask-unsign PoC).
f79a8f4 to
67101ba
Compare
5fc5af4 to
bf027b8
Compare
…cs polish - auth.py: Fix naive datetime comparisons that can crash with tz-aware Firestore timestamps - auth.py: Remove unused setup_url variable - bot.py: Fix naive datetime comparisons and store UTC-aware timestamps - user_commands.py: Replace deprecated datetime.utcnow() with datetime.now(timezone.utc) - notification_service.py: Replace deprecated utcnow(), update type hints to str | None - .env.example: Add trailing newline for dotenv compatibility - Dockerfile: Add --no-cache-dir to pip upgrade (consistent with other pip installs) - README.md: Fix Step 4 -> Step 3 references for Cloud Run URL - MAINTAINER.md: Replace absolute file:// paths with relative repo paths
bf027b8 to
1062fdf
Compare
…cs polish - auth.py: Fix naive datetime comparisons that can crash with tz-aware Firestore timestamps - auth.py: Remove unused setup_url variable - bot.py: Fix naive datetime comparisons and store UTC-aware timestamps - user_commands.py: Replace deprecated datetime.utcnow() with datetime.now(timezone.utc) - notification_service.py: Replace deprecated utcnow(), update type hints to str | None - .env.example: Add trailing newline for dotenv compatibility - Dockerfile: Add --no-cache-dir to pip upgrade (consistent with other pip installs) - README.md: Fix Step 4 -> Step 3 references for Cloud Run URL - MAINTAINER.md: Replace absolute file:// paths with relative repo paths
…command
- fix: trigger_sync() now always fetches REPO_OWNER's GitHub App installation
token for workflow dispatch — fixes 403 when the calling user's org differs
from the pipeline repo owner
- feat: /sync responses now use Discord embeds with colour coding
• yellow — sync on cooldown (12 h), notes pipeline may have failed
• green — workflow dispatch accepted
• red — specific error per HTTP status (403 permission, 404 not found,
422 branch/file missing, timeout, unknown)
- fix: sync status stored as 'dispatched' (was 'success') — bot only knows
the API call succeeded, not whether the pipeline run itself passed
- refactor: extract GitHubAppService.list_installations() so
find_installation_id() delegates to it instead of duplicating the HTTP call
- feat: add /help command with three embeds (Getting Started, Good to Know,
Admin Commands — last shown only to admins)
- fix: /add_repo and /remove_repo now validate the repo owner matches the
server's configured GitHub org before proceeding
- chore: disable /add_repo, /remove_repo, /list_repos registrations (webhook
handler is inactive); left commented-out for future re-enable
- docs: add REPO_OWNER / REPO_NAME / WORKFLOW_REF to .env.example (no comments
— strict line-by-line validator requires exact match)
- feat: env_validator.py — add REPO_OWNER (required=False), REPO_NAME and
WORKFLOW_REF (optional, warn if empty, document defaults)
- docs(MAINTAINER.md): add 'Setting Up /sync' section with step-by-step
instructions (enable Actions R&W permission, set env vars, re-accept install)
Author
Committed Changes (30 commits, from
|
| Area | What Changed |
|---|---|
| Multi-tenant Firestore | All data migrated to organizations/{github_org}/... paths. One GitHub org can serve multiple Discord servers. shared/firestore.py updated with get_mt_client() and org-scoped read/write helpers. |
| Sync → Async | All Firestore calls wrapped in await asyncio.to_thread() to avoid blocking the Discord event loop. Cross-thread infrastructure added so Flask can signal the Discord bot via call_soon_threadsafe. Analytics, config, user, and admin commands all async. |
Event-driven /link |
Replaced global lock + polling with per-user asyncio.Event. Flask OAuth callback signals the Discord bot thread directly. No more busy-wait. |
| UI Redesign | All setup/OAuth pages rebuilt — dark theme, Inter font, 460 px card, gradient buttons, SVG icons. Consistent across invite, setup, callback, and error pages. render_status_page() helper centralises all status/error rendering; 14+ plain error strings replaced with styled pages. |
| Owner approval flow | github_app_setup() restructured into 3 explicit cases: CASE 1 — owner approved from GitHub → shows "Run /setup again"; CASE 2 — non-owner member requested install → shows "Tell owner to approve on GitHub"; CASE 3 — happy path → saves config to Firestore and triggers initial sync. |
| PR Automation disabled | /set_webhook command removed. Webhook handler at /github/webhook returns early. All code kept in place for future re-enablement. /add_repo, /remove_repo, /list_repos command registrations commented out to match. |
| OAuth session cleanup | Background thread cleans up sessions older than 10 minutes. Prevents memory leak on long-running deployments. |
| Setup state expiration | Extended from 10 minutes to 7 days via itsdangerous max_age to handle async org-owner approvals that may take days. |
SECRET_KEY |
Added to .env.example. Auto-generates a random value if left blank during deploy.sh. Used for session signing and state JWTs. |
/sync command |
New admin-only slash command to manually trigger the data pipeline. 12 h cooldown after a successful dispatch; immediate retry allowed after a failure. Stores last_sync_at, last_sync_status, last_sync_error in Firestore. Responses use coloured embeds — 🟡 yellow (cooldown, notes pipeline may have failed), 🟢 green (dispatch accepted), 🔴 red (specific message per HTTP status: 403 / 404 / 422 / timeout / unknown). |
fix: /sync 403 |
trigger_sync() now always fetches the REPO_OWNER installation token for workflow dispatch. Previously it used the calling user's org token, which caused a 403 when that org didn't own the pipeline repo. |
Sync status → "dispatched" |
Was stored as "success", implying the pipeline run passed. Renamed to "dispatched" since the bot only knows the API call was accepted. Cooldown copy updated to match. |
REPO_OWNER / REPO_NAME / WORKFLOW_REF |
Three new env vars added to .env.example (no comments — strict line-by-line validator requires exact match) and to env_validator.py FIELD_CONFIG: REPO_OWNER (required=False), REPO_NAME and WORKFLOW_REF (optional, warning if empty, documents defaults). |
/help command |
New user-facing slash command with three embeds: Getting Started (setup → link → stats), Good to Know (update schedule, new-repo caveats, empty-stats FAQ), Admin Commands (only shown when caller has Administrator permission). |
| Org scope validation | /add_repo and /remove_repo now validate that the repo owner matches the server's configured GitHub org. Prevents cross-org monitoring. |
GitHubAppService.list_installations() |
Extracted as a standalone method; find_installation_id() now delegates to it instead of duplicating the HTTP call. |
| Role hierarchy validation | /configure roles validates role hierarchy before granting to prevent privilege escalation. |
| Timezone fixes | All datetime.utcnow() replaced with datetime.now(timezone.utc) (deprecated API removed). |
Security: revert org-picker on /setup |
A draft added a picker listing all GitHub App installations so any Discord admin could choose which org to link — a privilege escalation risk. Reverted to a plain GitHub redirect that enforces GitHub's own permission model. |
MAINTAINER.md |
New file: environment variables reference, how to re-enable PR automation, async architecture patterns, multi-tenant Firestore design, and a Setting Up /sync section (enable Actions R&W permission in the GitHub App, set the three env vars, redeploy, re-accept permissions on the installation page). |
| Various bug fixes | NoneType in webhook status, resource leaks in notification system, asyncio event loop errors in pr_review, try/except import pattern for package compatibility, deploy.sh read -rp → read -p and SECRET_KEY display fix. |
Uncommitted Changes
None — all changes committed.
- _save_sync_metadata now checks set_server_config return value and prints a warning if the Firestore write fails, instead of silently dropping cooldown metadata - env_validator: add warning_if_empty for REPO_OWNER and document the ruxailab default (matching the actual os.getenv default in auth.py)
Root cause: discord.utils.get() returns only the first matching category. If /setup_voice_stats and the pipeline's _update_channels_for_guild both run near-simultaneously (e.g. first deploy + immediate /sync), neither finds an existing category and both create one, leaving two duplicates. Fix: - guild_service._update_channels_for_guild: scan for ALL categories named 'REPOSITORY STATS', keep the first, delete channels + category for any extras before proceeding with the update - admin_commands.setup_voice_stats: same scan; if duplicates are found it cleans them up and reports the result instead of just saying 'already exists'
6c62d86 to
8eb152b
Compare
Optional vars (REPO_OWNER, REPO_NAME, WORKFLOW_REF) may be absent when the maintainer does not need /sync. The strict exact-line-count check was blocking deploy.sh validation for any .env that omits those lines. Change: only error if .env has MORE lines than .env.example (unexpected extras). Fewer lines are allowed — FIELD_CONFIG already handles missing optional vars as warnings and missing required vars as errors. Field requirement validation gate updated from == to <= accordingly.
…t/create flows Both create_new_env_file() and edit_env_file() previously wrote only 9 lines, causing validation to fail with 'expected 12 lines, found 9'. Both functions now prompt for the 3 optional /sync vars and write all 12 lines to match .env.example exactly. - create_new_env_file: shows a brief optional hint with defaults - edit_env_file: shows current value as default, uses env default as fallback (REPO_OWNER -> ruxailab, REPO_NAME -> disgitbot, etc.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
will do the checks manually first & try everything, will ping you if i dont find problem
btw the twice message in discord is because i have two service on cloud run url, that one is fixed.