StackPilot called `docker compose -p <folder> -f compose.yaml`, which is
not what `docker compose` typed in the stack's folder does. A
compose.override.yaml or a COMPOSE_FILE in .env applied in a terminal
and was dropped by the next Start in StackPilot, which recreated the
containers without it. A compose file's `name:` or a COMPOSE_PROJECT_NAME
in .env made the terminal's containers invisible: the stack showed as
stopped while running, and Start set up a second copy next to it.
Compose now runs in the stack's folder without -f, so it loads the same
files, and -p is the name compose itself picks there: COMPOSE_PROJECT_NAME
from .env, else the last `name:` among the compose files (${VAR} from
.env), else the folder name. A stack StackPilot already runs under its
folder name keeps that name while it has containers or volumes under it
and none under the declared one: switching would have brought it up
again on empty volumes. Backups record the project, and a restore takes
it from the files it has just put back.
Changes made outside StackPilot now reach every view:
- docker_watch_service reads Docker's events once for the backend. The
dashboard rollup (cached 25s) and the stats cache are stale as soon as
the event generation moves; the tiles followed a terminal `compose
stop` after 15-30s, now within a second.
- Image events re-read the local digests, so a `docker compose pull` in
a terminal clears the update badge instead of leaving it until the
next registry check (an hour by default).
- The browser catches up when its event socket reconnects; what changed
while it was down waited for the 60s poll.
- The stack detail carries a revision of the compose file and .env;
saving after they changed on disk is refused with 409 and the editor
offers to overwrite or to load the version on disk. A save used to
replace a terminal edit without a word.
- A stack whose folder is gone is flagged "folder missing" instead of
passing for an ordinary stopped stack.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fx7gcuy8d6nPd95g43rGHu
61 lines
1.7 KiB
Python
61 lines
1.7 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 Stack(SQLModel, table=True):
|
|
# id is the stack directory name (slug)
|
|
id: str = Field(primary_key=True)
|
|
name: str
|
|
description: Optional[str] = None
|
|
# None = automatic (the UI derives one from the name), "lucide:<name>" for a
|
|
# built-in icon, "custom:<ext>:<version>" for an uploaded image.
|
|
# See services/icon_service.py.
|
|
icon: Optional[str] = None
|
|
stacks_dir_override: Optional[str] = None
|
|
created_at: datetime = Field(default_factory=_now)
|
|
updated_at: datetime = Field(default_factory=_now)
|
|
|
|
|
|
# --- API schemas ---
|
|
|
|
|
|
class StackCreate(SQLModel):
|
|
name: str
|
|
description: Optional[str] = None
|
|
icon: Optional[str] = None # "lucide:<name>", or None/"" for automatic
|
|
yaml: Optional[str] = None # initial compose content
|
|
env: Optional[str] = None
|
|
|
|
|
|
class StackUpdate(SQLModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
# Omitted leaves the icon alone; "" resets it to automatic.
|
|
icon: Optional[str] = None
|
|
yaml: Optional[str] = None
|
|
env: Optional[str] = None
|
|
#: The ``revision`` the editor loaded. When it no longer matches the files,
|
|
#: they were changed elsewhere (a terminal, git) and the save is refused
|
|
#: instead of silently overwriting that change. Omitted: no check.
|
|
base_revision: Optional[str] = None
|
|
|
|
|
|
class StackCloneRequest(SQLModel):
|
|
name: str
|
|
|
|
|
|
class ConvertRequest(SQLModel):
|
|
command: str # a `docker run ...` string
|
|
|
|
|
|
class ConvertResponse(SQLModel):
|
|
yaml: str
|