mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
105 lines
4.4 KiB
Python
Executable File
105 lines
4.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# ruff: noqa: T201 — console output is the point of this bootstrap script
|
|
"""Idempotent local-dev bootstrap.
|
|
|
|
Runs on every `insightscli start` (see bin/mprocs.yaml) and from `insightscli dev:reset`. Every step must be
|
|
idempotent and safe to re-run. Callers ensure the database and required tables are ready before
|
|
invoking this: the mprocs unit waits via bin/wait-for-postgres-tables, and dev:reset runs it after
|
|
migrations — so this script does no waiting of its own.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
import django
|
|
|
|
# This script lives in bin/, so the repo root isn't on sys.path the way it is for manage.py.
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "insights.settings")
|
|
django.setup()
|
|
|
|
from django.conf import settings # noqa: E402
|
|
from django.core.management import call_command # noqa: E402
|
|
|
|
from insights.models import OAuthApplication # noqa: E402
|
|
from insights.scopes import UNPRIVILEGED_SCOPES # noqa: E402
|
|
|
|
# The wizard requests @hanzo/wizard's WIZARD_OAUTH_SCOPES plus per-program additions
|
|
# (PROGRAM_SCOPE_ADDITIONS), and any single scope outside the app's ceiling fails the whole
|
|
# /authorize with invalid_scope. Enumerating the union here would break every time a program
|
|
# adds a scope, so grant everything unprivileged plus the privileged/hidden scopes the wizard
|
|
# needs — same broad ceiling the hedgebox demo matrix sets on this app. Local-dev only.
|
|
WIZARD_SCOPES = sorted(
|
|
UNPRIVILEGED_SCOPES | {"llm_gateway:read", "llm_gateway:write", "wizard_session:read", "wizard_session:write"}
|
|
)
|
|
|
|
|
|
def ensure_wizard_oauth_app() -> None:
|
|
"""Create the wizard cloud-run OAuth app the backend mints tokens under.
|
|
|
|
Inlined rather than given its own management command — it only ever runs from here. The client_id
|
|
is a committed, local-only id (WIZARD_CLOUD_RUN_OAUTH_CLIENT_ID), not @hanzo/wizard's dev/QA id.
|
|
"""
|
|
if not settings.DEBUG or settings.CLOUD_DEPLOYMENT:
|
|
print(" not a local dev environment - skipping wizard OAuth app")
|
|
return
|
|
|
|
# DEBUG=True alone is not a reliable "this is a laptop" signal (misconfigured self-hosted
|
|
# instances run with it), and this app is a public client with a committed client_id and a
|
|
# broad scope ceiling - require a localhost SITE_URL too before seeding it.
|
|
site_url = getattr(settings, "SITE_URL", "") or ""
|
|
if "//localhost" not in site_url and "//127.0.0.1" not in site_url:
|
|
print(f" SITE_URL ({site_url}) is not localhost - skipping wizard OAuth app")
|
|
return
|
|
|
|
client_id = settings.WIZARD_CLOUD_RUN_OAUTH_CLIENT_ID
|
|
if not client_id:
|
|
print(" WIZARD_CLOUD_RUN_OAUTH_CLIENT_ID not set - skipping wizard OAuth app")
|
|
return
|
|
|
|
# The wizard's OAuth login flow uses a loopback callback on an ephemeral port (unused by the
|
|
# headless cloud run). A portless localhost URI matches any port — see validate_redirect_uri in
|
|
# insights/api/oauth/views.py, which extends RFC 8252 §7.3 loopback flexibility to localhost.
|
|
redirect_uris = "http://localhost/callback"
|
|
app, created = OAuthApplication.objects.get_or_create(
|
|
client_id=client_id,
|
|
defaults={
|
|
"name": "Insights Wizard (local dev)",
|
|
"client_type": OAuthApplication.CLIENT_PUBLIC,
|
|
"authorization_grant_type": OAuthApplication.GRANT_AUTHORIZATION_CODE,
|
|
"redirect_uris": redirect_uris,
|
|
"algorithm": "RS256",
|
|
"scopes": WIZARD_SCOPES,
|
|
},
|
|
)
|
|
if not created and sorted(app.scopes) != sorted(WIZARD_SCOPES):
|
|
app.scopes = WIZARD_SCOPES
|
|
app.save(update_fields=["scopes"])
|
|
status = "updated scopes for"
|
|
else:
|
|
status = "created" if created else "already have"
|
|
print(f" {status} wizard OAuth app (client_id={client_id})")
|
|
|
|
|
|
# Logs which step it is running so the output clearly shows what got set up. A failing step warns and
|
|
# the rest still run — local setup must never abort the dev stack.
|
|
STEPS = [
|
|
("dev personal API key", lambda: call_command("setup_local_api_key")),
|
|
("wizard cloud-run OAuth app", ensure_wizard_oauth_app),
|
|
]
|
|
|
|
|
|
def main() -> None:
|
|
print("=== ensure-local-setup: local-dev bootstrap ===")
|
|
for label, fn in STEPS:
|
|
print(f"\n→ {label}")
|
|
try:
|
|
fn()
|
|
except Exception as e:
|
|
print(f" WARNING: '{label}' failed: {e}")
|
|
print("Local setup complete")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|