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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
79d82361d8
commit
786c346c40
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "stackpilot-frontend",
|
||||
"private": true,
|
||||
"version": "0.32.1",
|
||||
"version": "0.33.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -3,7 +3,7 @@ import api from "./client";
|
||||
export interface BackupDestination {
|
||||
id: number;
|
||||
name: string;
|
||||
type: "sftp" | "s3";
|
||||
type: "sftp" | "s3" | "nfs";
|
||||
config: Record<string, string>;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
@@ -342,6 +342,12 @@ const FIELDS: Record<string, { key: string; label: string; secret?: boolean; are
|
||||
{ key: "secret_key", label: "Secret key", secret: true },
|
||||
{ key: "prefix", label: "Key prefix (optional)", placeholder: "stackpilot/" },
|
||||
],
|
||||
nfs: [
|
||||
{ key: "server", label: "NFS server (host or IP)", placeholder: "10.0.0.5" },
|
||||
{ key: "path", label: "Export path", placeholder: "/export/backups" },
|
||||
{ key: "options", label: "Mount options", placeholder: "rw,nfsvers=4 (default: rw)" },
|
||||
{ key: "subdir", label: "Subdirectory inside the export (optional)", placeholder: "stackpilot" },
|
||||
],
|
||||
};
|
||||
|
||||
function DestinationsSection() {
|
||||
@@ -362,8 +368,8 @@ function DestinationsSection() {
|
||||
{data?.length === 0 && !adding && (
|
||||
<Card>
|
||||
<p className="text-sm text-slate-500">
|
||||
No destinations. Add an SFTP server or S3-compatible bucket to push stack
|
||||
backups off-box and restore from them.
|
||||
No destinations. Add an SFTP server, S3-compatible bucket or NFS share to
|
||||
push stack backups off-box and restore from them.
|
||||
</p>
|
||||
</Card>
|
||||
)}
|
||||
@@ -395,7 +401,9 @@ function DestinationRow({ dest, onChange }: { dest: BackupDestination; onChange:
|
||||
const summary =
|
||||
dest.type === "s3"
|
||||
? `${dest.config.bucket}${dest.config.prefix ? "/" + dest.config.prefix : ""}`
|
||||
: `${dest.config.username}@${dest.config.host}:${dest.config.path || "."}`;
|
||||
: dest.type === "nfs"
|
||||
? `${dest.config.server}:${dest.config.path}${dest.config.subdir ? "/" + dest.config.subdir : ""}`
|
||||
: `${dest.config.username}@${dest.config.host}:${dest.config.path || "."}`;
|
||||
|
||||
return (
|
||||
<Card className="flex flex-wrap items-center justify-between gap-2">
|
||||
@@ -443,6 +451,7 @@ function DestinationForm({ onDone, onCancel }: { onDone: () => void; onCancel: (
|
||||
<select className={selectClass} value={type} onChange={(e) => { setType(e.target.value); setConfig({}); }}>
|
||||
<option value="sftp">SFTP</option>
|
||||
<option value="s3">S3-compatible</option>
|
||||
<option value="nfs">NFS share</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user