mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-12 15:33:45 +08:00
Sentry: Tidy comments and drop swallowed errors
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
# Automatic Sentry relay enrollment
|
||||
|
||||
Implemented across [StarPilot #18](https://github.com/dirwin31/openpilot/pull/18), [Android/Worker #3](https://github.com/dirwin31/StarPilotAndroid/pull/3), and [iOS #4](https://github.com/natehuby76/ios-clone/pull/4). StarPilot is based on FixesTest; Android is based on Play-Dev.
|
||||
|
||||
## User flow
|
||||
|
||||
The user connects the phone app to comma and enables alerts. The app requests OS notification permission, registers its FCM/APNs token with comma, and asks comma to enroll automatically. Galaxy's Enable alerts action starts the same flow. Users never copy keys, invitations, tokens or URLs. Each phone must grant permission; comma and phone need internet for delivery.
|
||||
|
||||
The relay is deployed with its provider credentials, and `DEFAULT_RELAY_URL` in `openpilot/starpilot/sentry_mode/relay_config.py` points at it (`https://galaxy-sentry-relay.dannyirwin31.workers.dev`). With an empty value the UI reports that hosting is not configured and does not claim success. The apps discover readiness from comma and need no duplicate URL configuration.
|
||||
|
||||
Enabling is explicit. Existing subscriptions or app upgrades do not silently opt into a new relay. Custom invitation-based relays remain available under advanced controls; they are not the normal user flow.
|
||||
|
||||
## Credential ownership and recovery
|
||||
|
||||
Comma creates a random installation ID and 256-bit sending secret and saves them privately before enrollment. It sends only the SHA-256 digest at enrollment. Later requests use an installation bearer credential. The Worker stores digests; phone apps never receive the comma credential. Provider keys remain Worker secrets. Snapshots expose only capability, lifecycle state, installation ID and destination count.
|
||||
|
||||
Pending enrollment, rotation, revocation and destination reconciliation survive restarts. Requests use HTTPS without redirects; credentials are bound to the configured origin. A lost enrollment response is recovered by authenticating with the saved candidate. Rotation checks the candidate before retrying, including after the old credential's ten-minute overlap expires. Confirmation retires the old credential.
|
||||
|
||||
Revocation stops new local sending immediately and retries remote revocation offline. The Worker keeps a revocation tombstone so retries cannot reactivate the old ID. An already authorized provider request may finish. Forgetting locally does not revoke a copied credential. An explicit new enrollment generates a new ID; anonymous enrollment cannot prove that it represents the same hardware.
|
||||
|
||||
## Public enrollment and capacity
|
||||
|
||||
Automatic enrollment is intentionally public. It does not provide hardware attestation or verified ownership. The Worker requires operator configuration and uses transactional Durable Object state for signup limits, credential checks, destination scope, revocation, send quotas and deduplication. Approximate ingress rate limiting adds another layer.
|
||||
|
||||
Default limits are 5 new installations per IP/day, 100 per relay/day, 1,000 total automatic installations including revoked IDs, 8 destinations per installation, 60 sends/hour/installation and 1,000 sends/day/relay. Operators can change these bounded settings. Capacity exhaustion fails closed. Daily IP hashes expire after two days and are pruned by an alarm. Event receipts expire after a day and are pruned on later sends.
|
||||
|
||||
An attacker can consume capacity or generate new identities. These controls limit resource use; they do not establish physical-device identity. A stronger trust boundary would require accounts, attestation or a challenge, which is outside the one-action anonymous flow. A registered installation can only send to its own registered destinations; provider-token secrecy still matters because this phase does not prove phone ownership cryptographically.
|
||||
|
||||
## Protocol
|
||||
|
||||
| Route | Purpose |
|
||||
| --- | --- |
|
||||
| `POST /v2/enroll` | Register installation and digest; an empty invitation selects automatic enrollment |
|
||||
| `GET /v2/installation` | Recover enrollment or rotation with the saved credential |
|
||||
| `PUT /v2/subscriptions/{id}` | Register or replace a scoped destination |
|
||||
| `DELETE /v2/subscriptions/{id}` | Remove a destination |
|
||||
| `POST /v2/sentry` | Send an allowlisted, bounded event to a registered destination |
|
||||
| `POST /v2/credentials/rotate` | Activate a replacement with bounded overlap |
|
||||
| `POST /v2/credentials/confirm` | Retire the old credential |
|
||||
| `DELETE /v2/installation` | Revoke the installation |
|
||||
|
||||
Operator-only routes issue optional private invitations and revoke installations. Admin credentials never enter apps or comma. Provider responses are acceptance, not proof of display; interrupted provider responses may lead to duplicates. Camera media stays on comma. Authentication failures pause v2 delivery; they never fall back to anonymous or legacy sending.
|
||||
|
||||
## Setup and validation
|
||||
|
||||
The relay is running (see [HOSTING.md](https://github.com/dirwin31/StarPilotAndroid/blob/sentry-cloudflare/push-relay/HOSTING.md)). Remaining work is registering the iOS app and Nate's APNs key in Firebase, signed app builds and delivery tests on real phones.
|
||||
|
||||
Focused notification/recovery tests, Galaxy browser checks, Worker unit tests, Android Play builds/tests and iOS production-model regressions cover the implementation. The Worker was also run locally in the Cloudflare runtime against the real Firebase project. A relay rejecting one phone skips that phone instead of blocking the comma. Full Galaxy HTTP tests need Linux GUI dependencies; full iOS compilation needs upstream's newer SDK. Live delivery to phones has not been tested.
|
||||
@@ -1997,20 +1997,16 @@ def make_server(*, port=8082, host='127.0.0.1', monitor=None, owner=None, crashe
|
||||
if identity is None:
|
||||
self.json(401, {'error': 'Sign in to Galaxy'})
|
||||
return
|
||||
try:
|
||||
from openpilot.common.params import Params
|
||||
params = Params()
|
||||
with effect_lock:
|
||||
if 'enabled' in payload:
|
||||
params.put_bool('CabinWhiteBalance', payload['enabled'])
|
||||
enabled = params.get_bool('CabinWhiteBalance')
|
||||
except Exception:
|
||||
self.json(503, {'error': 'White balance is unavailable'})
|
||||
from openpilot.common.params import Params
|
||||
params = Params()
|
||||
with effect_lock:
|
||||
if 'enabled' in payload:
|
||||
params.put_bool('CabinWhiteBalance', payload['enabled'])
|
||||
enabled = params.get_bool('CabinWhiteBalance')
|
||||
if self.settings_session() == identity:
|
||||
self.json(200, {'enabled': enabled})
|
||||
else:
|
||||
if self.settings_session() == identity:
|
||||
self.json(200, {'enabled': enabled})
|
||||
else:
|
||||
self.json(401, {'error': 'Sign in to Galaxy'})
|
||||
self.json(401, {'error': 'Sign in to Galaxy'})
|
||||
return
|
||||
if path == '/api/galaxy/device-name':
|
||||
if not self.require_session():
|
||||
|
||||
@@ -31,10 +31,7 @@ def _external_shader(fragment: str) -> str:
|
||||
return result.replace(yuv, "vec3 rgb = pow(texture(texture0, uv).rgb, vec3(1.0/1.28));")
|
||||
|
||||
|
||||
# The cabin camera has no IR-cut filter, so the window crops come out red/magenta.
|
||||
# Gray-world white balance over the crop being shown. Tuned on a daytime frame
|
||||
# (2026-11-04): full strength made gray pavement and a silver car neutral, and the
|
||||
# gain clamp keeps a single strongly colored object from being over-corrected.
|
||||
# Gray-world white balance for the cabin camera's magenta cast.
|
||||
WB_STRENGTH = 1.0
|
||||
WB_GAIN_MIN, WB_GAIN_MAX = 0.6, 1.6
|
||||
WB_SAMPLES = 16 # 16x16 grid of NV12 samples per crop
|
||||
@@ -99,7 +96,7 @@ class _WhiteBalance:
|
||||
|
||||
|
||||
class _WhiteBalanceSwitch:
|
||||
"""The saved Galaxy choice, re-read about once a second; on when unreadable."""
|
||||
"""The saved Galaxy choice, re-read about once a second."""
|
||||
|
||||
def __init__(self):
|
||||
self._enabled = True
|
||||
@@ -109,13 +106,10 @@ class _WhiteBalanceSwitch:
|
||||
def enabled(self, now: float) -> bool:
|
||||
if self._read_at is None or now - self._read_at >= WB_PARAM_INTERVAL or now < self._read_at:
|
||||
self._read_at = now
|
||||
try:
|
||||
if self._params is None:
|
||||
from openpilot.common.params import Params
|
||||
self._params = Params()
|
||||
self._enabled = bool(self._params.get_bool(WB_PARAM))
|
||||
except Exception:
|
||||
self._enabled = True
|
||||
if self._params is None:
|
||||
from openpilot.common.params import Params
|
||||
self._params = Params()
|
||||
self._enabled = self._params.get_bool(WB_PARAM)
|
||||
return self._enabled
|
||||
|
||||
|
||||
@@ -311,7 +305,7 @@ class PiPRenderer:
|
||||
flip = np.array([1 if invert else 0], dtype=np.int32)
|
||||
rl.set_shader_value(shader, rl.get_shader_location(shader, "uFlipX"), flip,
|
||||
rl.ShaderUniformDataType.SHADER_UNIFORM_INT)
|
||||
# The comma samples gamma-adjusted RGB (pow 1/1.28), so a pre-gamma gain g becomes g^(1/1.28).
|
||||
# The comma samples gamma-adjusted RGB, so the gain is raised to 1/1.28.
|
||||
shader_gain = np.power(np.array(gain, dtype=np.float32), 1.0 / 1.28) if COMMA_HARDWARE else np.array(gain, dtype=np.float32)
|
||||
rl.set_shader_value(shader, rl.get_shader_location(shader, "uGain"), shader_gain,
|
||||
rl.ShaderUniformDataType.SHADER_UNIFORM_VEC3)
|
||||
|
||||
@@ -62,9 +62,7 @@ void main() {
|
||||
float y = texture(texture0, uv).r;
|
||||
vec2 c = texture(texture1, uv).ra - 0.5;
|
||||
vec3 rgb = vec3(y + 1.402 * c.y, y - 0.344 * c.x - 0.714 * c.y, y + 1.772 * c.x);
|
||||
// Cancel the cabin camera's IR (no IR-cut filter) magenta cast; leave clipped highlights white.
|
||||
// The cast is strongest through the glass and absent in open air, so one gain can't fix both:
|
||||
// a mild desaturation hides what's left (pink foliage, a slightly cyan sky).
|
||||
// Cancel the cabin camera's magenta cast; leave clipped highlights white.
|
||||
rgb = mix(rgb * uGain, rgb, smoothstep(0.95, 1.0, max(rgb.r, max(rgb.g, rgb.b))));
|
||||
rgb = mix(vec3(dot(rgb, vec3(0.299, 0.587, 0.114))), rgb, uSaturation);
|
||||
|
||||
@@ -138,9 +136,7 @@ void main() {
|
||||
float y = texture(texture0, uv).r;
|
||||
vec2 c = texture(texture1, uv).ra - 0.5;
|
||||
vec3 rgb = vec3(y + 1.402 * c.y, y - 0.344 * c.x - 0.714 * c.y, y + 1.772 * c.x);
|
||||
// Cancel the cabin camera's IR (no IR-cut filter) magenta cast; leave clipped highlights white.
|
||||
// The cast is strongest through the glass and absent in open air, so one gain can't fix both:
|
||||
// a mild desaturation hides what's left (pink foliage, a slightly cyan sky).
|
||||
// Cancel the cabin camera's magenta cast; leave clipped highlights white.
|
||||
rgb = mix(rgb * uGain, rgb, smoothstep(0.95, 1.0, max(rgb.r, max(rgb.g, rgb.b))));
|
||||
rgb = mix(vec3(dot(rgb, vec3(0.299, 0.587, 0.114))), rgb, uSaturation);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user