"""Satellite model - remote agent at a customer site. The API key is stored as a SHA-256 hash; the plaintext key is shown exactly once at creation time. """ import hashlib import secrets from datetime import UTC, datetime from sqlalchemy import Boolean, DateTime, ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.database import Base def generate_api_key() -> str: """Generate a new satellite API key (plaintext, show once).""" return f"ius_{secrets.token_urlsafe(32)}" def hash_api_key(key: str) -> str: return hashlib.sha256(key.encode()).hexdigest() class Satellite(Base): __tablename__ = "satellites" id: Mapped[int] = mapped_column(primary_key=True) customer_id: Mapped[int] = mapped_column( ForeignKey("customers.id"), index=True ) customer: Mapped["Customer"] = relationship(back_populates="satellites") # noqa: F821 name: Mapped[str] = mapped_column(String(255)) api_key_hash: Mapped[str] = mapped_column(String(64), unique=True, index=True) api_key_prefix: Mapped[str] = mapped_column(String(12)) # for display in UI is_active: Mapped[bool] = mapped_column(Boolean, default=True) # Filled by heartbeat last_seen_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) version: Mapped[str | None] = mapped_column(String(50), nullable=True) hostname: Mapped[str | None] = mapped_column(String(255), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(UTC) ) jobs: Mapped[list["UpdateJob"]] = relationship( # noqa: F821 back_populates="satellite" )