feat(updater): remote self-update daemon with auto-rollback
Adds a separate `decnet updater` daemon on each worker that owns the agent's release directory and installs tarball pushes from the master over mTLS. A normal `/update` never touches the updater itself, so the updater is always a known-good rescuer if a bad agent push breaks /health — the rotation is reversed and the agent restarted against the previous release. `POST /update-self` handles updater upgrades explicitly (no auto-rollback). - decnet/updater/: executor, FastAPI app, uvicorn launcher - decnet/swarm/updater_client.py, tar_tree.py: master-side push - cli: `decnet updater`, `decnet swarm update [--host|--all] [--include-self] [--dry-run]`, `--updater` on `swarm enroll` - enrollment API issues a second cert (CN=updater@<host>) signed by the same CA; SwarmHost records updater_cert_fingerprint - tests: executor, app, CLI, tar tree, enroll-with-updater (37 new) - wiki: Remote-Updates page + sidebar + SWARM-Mode cross-link
This commit is contained in:
@@ -118,6 +118,10 @@ class SwarmHost(SQLModel, table=True):
|
||||
# ISO-8601 string of the last successful agent /health probe
|
||||
last_heartbeat: Optional[datetime] = Field(default=None)
|
||||
client_cert_fingerprint: str # SHA-256 hex of worker's issued client cert
|
||||
# SHA-256 hex of the updater-identity cert, if the host was enrolled
|
||||
# with ``--updater`` / ``issue_updater_bundle``. ``None`` for hosts
|
||||
# that only have an agent identity.
|
||||
updater_cert_fingerprint: Optional[str] = Field(default=None)
|
||||
# Directory on the master where the per-worker cert bundle lives
|
||||
cert_bundle_path: str
|
||||
enrolled_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
@@ -281,6 +285,17 @@ class SwarmEnrollRequest(BaseModel):
|
||||
description="Extra SANs (IPs / hostnames) to embed in the worker cert",
|
||||
)
|
||||
notes: Optional[str] = None
|
||||
issue_updater_bundle: bool = PydanticField(
|
||||
default=False,
|
||||
description="If true, also issue an updater cert (CN=updater@<name>) for the remote self-updater",
|
||||
)
|
||||
|
||||
|
||||
class SwarmUpdaterBundle(BaseModel):
|
||||
"""Subset of SwarmEnrolledBundle for the updater identity."""
|
||||
fingerprint: str
|
||||
updater_cert_pem: str
|
||||
updater_key_pem: str
|
||||
|
||||
|
||||
class SwarmEnrolledBundle(BaseModel):
|
||||
@@ -293,6 +308,7 @@ class SwarmEnrolledBundle(BaseModel):
|
||||
ca_cert_pem: str
|
||||
worker_cert_pem: str
|
||||
worker_key_pem: str
|
||||
updater: Optional[SwarmUpdaterBundle] = None
|
||||
|
||||
|
||||
class SwarmHostView(BaseModel):
|
||||
@@ -303,6 +319,7 @@ class SwarmHostView(BaseModel):
|
||||
status: str
|
||||
last_heartbeat: Optional[datetime] = None
|
||||
client_cert_fingerprint: str
|
||||
updater_cert_fingerprint: Optional[str] = None
|
||||
enrolled_at: datetime
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user