# plugins/host_agent/models.py from __future__ import annotations import uuid from datetime import datetime, timezone from sqlalchemy import Boolean, Column, String, DateTime, Float, ForeignKey, Integer, Text from steward.models.base import Base def _uuid() -> str: return str(uuid.uuid4()) def _utcnow() -> datetime: return datetime.now(timezone.utc) class HostAgentRegistration(Base): __tablename__ = "host_agent_registrations" id = Column(String(36), primary_key=True, default=_uuid) host_id = Column(String(36), ForeignKey("hosts.id", ondelete="CASCADE"), unique=True, nullable=False) token_hash = Column(String(64), nullable=False, unique=True, index=True) token_created_at = Column(DateTime(timezone=True), nullable=False, default=_utcnow) agent_version = Column(String(32), nullable=True) kernel = Column(String(128), nullable=True) distro = Column(String(128), nullable=True) arch = Column(String(32), nullable=True) # Agent-reported primary IP. 45 chars = max textual IPv6 (no zone suffix). host_ip = Column(String(45), nullable=True) last_seen_at = Column(DateTime(timezone=True), nullable=True) # How far this host's clock is from Steward's, in seconds, positive when # the host is ahead, measured from the agent's sent_at on the newest post; # and when that was. None until an agent that stamps its posts reports. clock_offset_s = Column(Float, nullable=True) clock_checked_at = Column(DateTime(timezone=True), nullable=True) # What the package manager has waiting, from the agent's hourly check # (agent ≥ 1.16.0, #5765). updates_security is None when the manager # can't say; updates_error is why the last check failed. updates_manager = Column(String(8), nullable=True) updates_pending = Column(Integer, nullable=True) updates_security = Column(Integer, nullable=True) updates_checked_at = Column(DateTime(timezone=True), nullable=True) updates_error = Column(String(200), nullable=True) # Whether the host needs a reboot (None: it can't say), since when, and # the packages that asked for it, comma-separated. reboot_required = Column(Boolean, nullable=True) reboot_required_since = Column(DateTime(timezone=True), nullable=True) reboot_packages = Column(Text, nullable=True) created_at = Column(DateTime(timezone=True), nullable=False, default=_utcnow) updated_at = Column(DateTime(timezone=True), nullable=False, default=_utcnow, onupdate=_utcnow) class HostDowntime(Base): """A span a host's agent was away, and what the agent said it was (#5756). kind is "reboot" (the host went down and came back), "agent_stop" (the agent stopped; the host stayed up), "unclean" (the host came back from a boot nobody announced: power loss or a crash) or "lost_contact" (the agent ran but couldn't reach Steward). ended_at is None while it is going on. """ __tablename__ = "host_downtime" id = Column(String(36), primary_key=True, default=_uuid) host_id = Column(String(36), ForeignKey("hosts.id", ondelete="CASCADE"), nullable=False, index=True) kind = Column(String(16), nullable=False) started_at = Column(DateTime(timezone=True), nullable=False) ended_at = Column(DateTime(timezone=True), nullable=True) created_at = Column(DateTime(timezone=True), nullable=False, default=_utcnow)