Skip to content

Comments

feat: multi webhook SaaS & secured debug/servers#61

Merged
tqmsh merged 34 commits intoruxailab:setupWizardfrom
tim48-robot:feature/saas-ready
Feb 20, 2026
Merged

feat: multi webhook SaaS & secured debug/servers#61
tqmsh merged 34 commits intoruxailab:setupWizardfrom
tim48-robot:feature/saas-ready

Conversation

@tim48-robot
Copy link

@tim48-robot tim48-robot commented Jan 12, 2026

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.

- 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
@tim48-robot tim48-robot marked this pull request as draft February 4, 2026 11:19
- 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).
@tim48-robot tim48-robot force-pushed the feature/saas-ready branch 4 times, most recently from 5fc5af4 to bf027b8 Compare February 12, 2026 09:45
…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
…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)
@tim48-robot
Copy link
Author

Committed Changes (30 commits, from 0f56f5b to 55ad0b6)

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 -rpread -p and SECRET_KEY display fix.

Uncommitted Changes

None — all changes committed.

@tim48-robot tim48-robot marked this pull request as ready for review February 20, 2026 17:51
- _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'
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.)
@tqmsh tqmsh merged commit b5ebe52 into ruxailab:setupWizard Feb 20, 2026
0 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants