Files
menzeljandClaude Fable 5 786c346c40 0.33.0: NFS share as backup destination
New destination type 'nfs' alongside SFTP and S3. The StackPilot container
needs no mount privileges: the Docker daemon mounts the export as a named
volume (stackpilot-nfs-dest-<id>, driver local/type nfs, recreated whenever
server/path/options change) and all file I/O runs through throwaway helper
containers (BACKUP_HELPER_IMAGE) — upload via put_archive, list via stat,
download via get_archive, delete/test via short-lived runs. Config: server,
export path, mount options (default rw), optional subdirectory (sanitized;
shell-safe charset). Mount failures surface as clean destination errors.

Settings UI gains the NFS form + summary; works everywhere destinations are
used (push, restore-from, scheduled backups incl. retention).

Verified live against a real kernel NFS server: test, push (file on the
export), list, restore-from incl. volume data, remote delete, config change
recreates the mount volume, unreachable server fails cleanly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:39:35 +00:00

49 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)
DESTINATION_TYPES = ["sftp", "s3", "nfs"]
# config keys that hold secrets — masked in API responses.
SECRET_KEYS = {"password", "private_key", "secret_key"}
class BackupDestination(SQLModel, table=True):
"""A remote target for stack backups (SFTP, S3-compatible or NFS)."""
id: Optional[int] = Field(default=None, primary_key=True)
name: str
type: str # one of DESTINATION_TYPES
config: str = "{}" # JSON-encoded, type-specific (incl. secrets)
created_at: datetime = Field(default_factory=_now)
# --- API schemas ---
class DestinationCreate(SQLModel):
name: str
type: str
config: dict
class DestinationUpdate(SQLModel):
name: Optional[str] = None
config: Optional[dict] = None
class DestinationRead(SQLModel):
id: int
name: str
type: str
config: dict # secrets masked
created_at: datetime