chore: sync local changes

This commit is contained in:
2026-03-26 22:44:08 -07:00
parent 52761c3b88
commit 812e98591d
17 changed files with 253 additions and 10 deletions
+82
View File
@@ -0,0 +1,82 @@
name: Build and Push to GHCR
on:
push:
branches: [main]
tags: ["*"]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: hanzoai/sentry
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Python build dependencies
run: |
pip install wheel setuptools
pip install -r requirements-base.txt
- name: Build Python wheel
run: |
python setup.py bdist_wheel
mkdir -p dist
pip freeze > dist/requirements-frozen.txt
- name: Build frontend
run: |
yarn install --frozen-lockfile
yarn build
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: self-hosted/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
SOURCE_COMMIT=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
+9 -9
View File
@@ -1,12 +1,12 @@
FROM python:3.11.6-slim-bullseye
LABEL maintainer="oss@sentry.io"
LABEL org.opencontainers.image.title="Sentry"
LABEL org.opencontainers.image.description="Sentry runtime image"
LABEL org.opencontainers.image.url="https://sentry.io/"
LABEL org.opencontainers.image.documentation="https://develop.sentry.dev/self-hosted/"
LABEL org.opencontainers.image.vendor="Functional Software, Inc."
LABEL org.opencontainers.image.authors="oss@sentry.io"
LABEL maintainer="ops@hanzo.ai"
LABEL org.opencontainers.image.title="Hanzo Sentry"
LABEL org.opencontainers.image.description="Hanzo Sentry — error tracking and performance monitoring"
LABEL org.opencontainers.image.url="https://sentry.hanzo.ai/"
LABEL org.opencontainers.image.documentation="https://docs.hanzo.ai/sentry/"
LABEL org.opencontainers.image.vendor="Hanzo AI, Inc."
LABEL org.opencontainers.image.authors="ops@hanzo.ai"
# add our user and group first to make sure their IDs get assigned consistently
RUN groupadd -r sentry && useradd -r -m -g sentry sentry
@@ -93,5 +93,5 @@ CMD ["run", "web"]
ARG SOURCE_COMMIT
ENV SENTRY_BUILD=${SOURCE_COMMIT:-unknown}
LABEL org.opencontainers.image.revision=$SOURCE_COMMIT
LABEL org.opencontainers.image.source="https://github.com/getsentry/sentry/tree/${SOURCE_COMMIT:-master}/"
LABEL org.opencontainers.image.licenses="https://github.com/getsentry/sentry/blob/${SOURCE_COMMIT:-master}/LICENSE"
LABEL org.opencontainers.image.source="https://github.com/hanzoai/sentry/tree/${SOURCE_COMMIT:-main}/"
LABEL org.opencontainers.image.licenses="Apache-2.0"
+12
View File
@@ -0,0 +1,12 @@
from django.apps import AppConfig
class Config(AppConfig):
name = "sentry.auth.providers.hanzo"
def ready(self):
from sentry.auth import register
from .provider import HanzoOAuth2Provider
register("hanzo", HanzoOAuth2Provider)
+32
View File
@@ -0,0 +1,32 @@
from sentry.http import safe_urlopen, safe_urlread
from sentry.utils import json
from .constants import USERINFO_URL
class HanzoApiError(Exception):
def __init__(self, message="", status=0):
self.status = status
super().__init__(message)
class HanzoClient:
def __init__(self, access_token):
self.access_token = access_token
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def _request(self, url):
headers = {"Authorization": f"Bearer {self.access_token}"}
req = safe_urlopen(url, headers=headers)
body = safe_urlread(req)
if req.status_code != 200:
raise HanzoApiError(f"Request failed: {body}", status=req.status_code)
return json.loads(body)
def get_user(self):
return self._request(USERINFO_URL)
@@ -0,0 +1,11 @@
import os
# IAM OIDC endpoints (hanzo.id)
AUTHORIZE_URL = os.environ.get("SENTRY_OIDC_AUTHORIZE_URL", "https://hanzo.id/oauth/authorize")
ACCESS_TOKEN_URL = os.environ.get("SENTRY_OIDC_TOKEN_URL", "https://hanzo.id/oauth/token")
USERINFO_URL = os.environ.get("SENTRY_OIDC_USERINFO_URL", "https://hanzo.id/oauth/userinfo")
CLIENT_ID = os.environ.get("SENTRY_OIDC_CLIENT_ID", "app-sentry")
CLIENT_SECRET = os.environ.get("SENTRY_OIDC_CLIENT_SECRET", "")
SCOPE = "openid profile email"
@@ -0,0 +1,62 @@
from collections.abc import Mapping
from typing import Any
from sentry.auth.exceptions import IdentityNotValid
from sentry.auth.providers.oauth2 import OAuth2Callback, OAuth2Login, OAuth2Provider
from .client import HanzoApiError, HanzoClient
from .constants import ACCESS_TOKEN_URL, AUTHORIZE_URL, CLIENT_ID, CLIENT_SECRET, SCOPE
from .views import FetchUser
class HanzoOAuth2Provider(OAuth2Provider):
access_token_url = ACCESS_TOKEN_URL
authorize_url = AUTHORIZE_URL
name = "Hanzo"
def get_client_id(self):
return CLIENT_ID
def get_client_secret(self):
return CLIENT_SECRET
def get_auth_pipeline(self):
return [
OAuth2Login(
authorize_url=self.authorize_url,
client_id=self.get_client_id(),
scope=SCOPE,
),
OAuth2Callback(
access_token_url=self.access_token_url,
client_id=self.get_client_id(),
client_secret=self.get_client_secret(),
),
FetchUser(),
]
def get_setup_pipeline(self):
return self.get_auth_pipeline()
def get_refresh_token_url(self):
return ACCESS_TOKEN_URL
def build_config(self, state):
return {}
def build_identity(self, state: Mapping[str, Any]) -> Mapping[str, Any]:
data = state["data"]
user_data = state["user"]
return {
"id": user_data.get("sub") or user_data.get("id", ""),
"email": user_data.get("email", ""),
"name": user_data.get("name", user_data.get("displayName", "")),
"data": self.get_oauth_data(data),
}
def refresh_identity(self, auth_identity):
with HanzoClient(auth_identity.data["access_token"]) as client:
try:
client.get_user()
except HanzoApiError as e:
raise IdentityNotValid(e)
+16
View File
@@ -0,0 +1,16 @@
from django.http import HttpResponse
from rest_framework.request import Request
from sentry.auth.view import AuthView
from .client import HanzoClient
class FetchUser(AuthView):
def dispatch(self, request: Request, helper) -> HttpResponse:
access_token = helper.fetch_state("data")["access_token"]
with HanzoClient(access_token) as client:
user = client.get_user()
helper.bind_state("user", user)
return helper.next_step()
+1
View File
@@ -409,6 +409,7 @@ INSTALLED_APPS: tuple[str, ...] = (
"sentry.eventstream",
"sentry.auth.providers.google.apps.Config",
"sentry.auth.providers.fly.apps.Config",
"sentry.auth.providers.hanzo.apps.Config",
"django.contrib.staticfiles",
"sentry.issues.apps.Config",
"sentry.feedback",
+10
View File
@@ -0,0 +1,10 @@
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="64" rx="8" fill="#000000"/>
<g transform="translate(8, 8) scale(0.716)">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="#ffffff"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#ffffff"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#ffffff"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#ffffff"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#ffffff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

+9 -1
View File
@@ -1 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><path d="M79 72a7.3 7.3 0 0 0 0-7.5L46.4 8A7.2 7.2 0 0 0 40 4.4 7.3 7.3 0 0 0 33.6 8L23.1 26.3l2.6 1.6a51.4 51.4 0 0 1 18.9 18.9 50.4 50.4 0 0 1 6.8 22.7H44a44.3 44.3 0 0 0-5.8-19A43.8 43.8 0 0 0 22 34.2l-2.7-1.5-9.8 17 2.6 1.6a24.9 24.9 0 0 1 12.1 18.2h-17a1.2 1.2 0 0 1-1-.6 1.2 1.2 0 0 1 0-1.3l4.8-8a17.7 17.7 0 0 0-5.4-3.2L1 64.6A7.3 7.3 0 0 0 1 72a7.3 7.3 0 0 0 6.4 3.7h23.2v-3.2a31.3 31.3 0 0 0-4-15.3 31.3 31.3 0 0 0-8.7-9.6l3.7-6.4a38 38 0 0 1 11.3 12.3 38.1 38.1 0 0 1 5.1 19v3h19.7v-3a57.5 57.5 0 0 0-7.8-28.8A57.5 57.5 0 0 0 31.5 24l7.5-13a1.2 1.2 0 0 1 1-.6 1.2 1.2 0 0 1 1 .6l32.7 56.5a1.2 1.2 0 0 1 0 1.2 1.1 1.1 0 0 1-1 .6H65v6.2h7.7a7.1 7.1 0 0 0 6.3-3.7z"/></svg>
<svg viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="#ffffff"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="#DDDDDD"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#ffffff"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#ffffff"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#ffffff"/>
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="#DDDDDD"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#ffffff"/>
</svg>

Before

Width:  |  Height:  |  Size: 741 B

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="#ffffff"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="#DDDDDD"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#ffffff"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#ffffff"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#ffffff"/>
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="#DDDDDD"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 576 B