- stackpilot-agent: slim token-guarded FastAPI (reuses compose_service) exposing stack CRUD/lifecycle/logs + system info; same image, different CMD. agent/ Dockerfile + compose + .env.example. - Central proxy: Agent model, agent_service (httpx ping/proxy + live status: online/offline/unauthorized + hostname/last_seen), routers/agents.py (CRUD + ping + proxied stacks/lifecycle/logs/system). - Frontend: Settings → Remote hosts (add/check/remove, connectivity dot); Stacks grouped by host; remote stack detail with lifecycle, live logs, compose/.env edit. Verified end-to-end: agent+main on a shared network — register (good/bad token), list/create/start/logs/delete remote stacks, offline detection (502). Remote backup destinations (SFTP/S3) deferred. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
1.1 KiB
Python
50 lines
1.1 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Optional
|
|
|
|
from sqlmodel import Field, SQLModel
|
|
|
|
|
|
def _now() -> datetime:
|
|
return datetime.now(timezone.utc)
|
|
|
|
|
|
class Agent(SQLModel, table=True):
|
|
"""A remote host running stackpilot-agent."""
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
name: str
|
|
url: str # e.g. http://10.0.0.5:5010
|
|
token: str # shared AGENT_TOKEN of that host
|
|
status: str = "unknown" # online | offline | unauthorized | unknown
|
|
hostname: Optional[str] = None # reported by the agent on ping
|
|
last_seen: Optional[datetime] = None
|
|
created_at: datetime = Field(default_factory=_now)
|
|
|
|
|
|
# --- API schemas ---
|
|
|
|
|
|
class AgentCreate(SQLModel):
|
|
name: str
|
|
url: str
|
|
token: str
|
|
|
|
|
|
class AgentUpdate(SQLModel):
|
|
name: Optional[str] = None
|
|
url: Optional[str] = None
|
|
token: Optional[str] = None
|
|
|
|
|
|
class AgentRead(SQLModel):
|
|
id: int
|
|
name: str
|
|
url: str
|
|
status: str
|
|
hostname: Optional[str]
|
|
last_seen: Optional[datetime]
|
|
created_at: datetime
|
|
token_set: bool
|