Compare commits
1 Commits
a53203d60d
...
fix/sync-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332ea26bce |
46
README.md
46
README.md
@@ -194,11 +194,8 @@ vaultik [--config <path>] store info
|
|||||||
* Requires `VAULTIK_AGE_SECRET_KEY` environment variable with age private key
|
* Requires `VAULTIK_AGE_SECRET_KEY` environment variable with age private key
|
||||||
* Optional path arguments to restore specific files/directories (default: all)
|
* Optional path arguments to restore specific files/directories (default: all)
|
||||||
* Downloads and decrypts metadata, fetches required blobs, reconstructs files
|
* Downloads and decrypts metadata, fetches required blobs, reconstructs files
|
||||||
* Preserves file permissions, timestamps (mtime), and ownership (ownership requires root)
|
* Preserves file permissions, timestamps, and ownership (ownership requires root)
|
||||||
* Handles symlinks and directories
|
* Handles symlinks and directories
|
||||||
* Note: ctime is recorded in the snapshot for informational purposes but is not
|
|
||||||
restored, as setting ctime is not possible through standard system calls on
|
|
||||||
most platforms
|
|
||||||
|
|
||||||
**prune**: Remove unreferenced blobs from remote storage
|
**prune**: Remove unreferenced blobs from remote storage
|
||||||
* Scans all snapshots for referenced blobs
|
* Scans all snapshots for referenced blobs
|
||||||
@@ -210,42 +207,6 @@ vaultik [--config <path>] store info
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## file metadata
|
|
||||||
|
|
||||||
vaultik records the following metadata for each file: path, size, mode
|
|
||||||
(permissions), uid, gid, mtime (modification time), ctime, and symlink
|
|
||||||
target.
|
|
||||||
|
|
||||||
### ctime semantics (platform-specific)
|
|
||||||
|
|
||||||
The `ctime` field has different meanings depending on the operating system:
|
|
||||||
|
|
||||||
| Platform | ctime value | Source |
|
|
||||||
|----------|-------------|--------|
|
|
||||||
| **macOS** | File birth (creation) time | `syscall.Stat_t.Birthtimespec` |
|
|
||||||
| **Linux** | Inode change time | `syscall.Stat_t.Ctim` |
|
|
||||||
| **Other** | Falls back to mtime | `os.FileInfo.ModTime()` |
|
|
||||||
|
|
||||||
**macOS (Darwin):** HFS+ and APFS filesystems natively track file creation
|
|
||||||
time. The `ctime` field contains the true file birth time — when the file was
|
|
||||||
first created on disk.
|
|
||||||
|
|
||||||
**Linux:** Most Linux filesystems do not expose file creation time through
|
|
||||||
standard Go APIs. The `ctime` field contains the inode change time, which is
|
|
||||||
updated whenever file metadata (permissions, ownership, link count) or content
|
|
||||||
changes. Linux ext4 (kernel 4.11+) and btrfs do track birth time via the
|
|
||||||
`statx()` syscall, but this is not exposed through Go's `os.FileInfo.Sys()`.
|
|
||||||
|
|
||||||
**Restore:** ctime is stored in the snapshot database for informational and
|
|
||||||
forensic purposes but is not restored to the filesystem. Setting ctime is not
|
|
||||||
possible through standard system calls on most Unix platforms — the kernel
|
|
||||||
manages ctime automatically.
|
|
||||||
|
|
||||||
When using in-memory filesystems (e.g. afero `MemMapFs` in tests), ctime falls
|
|
||||||
back to mtime since there is no underlying `syscall.Stat_t`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## architecture
|
## architecture
|
||||||
|
|
||||||
### s3 bucket layout
|
### s3 bucket layout
|
||||||
@@ -286,14 +247,11 @@ Snapshot IDs follow the format `<hostname>_<snapshot-name>_<timestamp>` (e.g., `
|
|||||||
CREATE TABLE files (
|
CREATE TABLE files (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
path TEXT NOT NULL UNIQUE,
|
path TEXT NOT NULL UNIQUE,
|
||||||
source_path TEXT NOT NULL DEFAULT '',
|
|
||||||
mtime INTEGER NOT NULL,
|
mtime INTEGER NOT NULL,
|
||||||
ctime INTEGER NOT NULL,
|
|
||||||
size INTEGER NOT NULL,
|
size INTEGER NOT NULL,
|
||||||
mode INTEGER NOT NULL,
|
mode INTEGER NOT NULL,
|
||||||
uid INTEGER NOT NULL,
|
uid INTEGER NOT NULL,
|
||||||
gid INTEGER NOT NULL,
|
gid INTEGER NOT NULL
|
||||||
link_target TEXT
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE file_chunks (
|
CREATE TABLE file_chunks (
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ type File struct {
|
|||||||
Path types.FilePath // Absolute path of the file
|
Path types.FilePath // Absolute path of the file
|
||||||
SourcePath types.SourcePath // The source directory this file came from (for restore path stripping)
|
SourcePath types.SourcePath // The source directory this file came from (for restore path stripping)
|
||||||
MTime time.Time
|
MTime time.Time
|
||||||
// CTime is the file creation/change time. On macOS this is the birth time
|
|
||||||
// (when the file was created). On Linux this is the inode change time
|
|
||||||
// (updated on metadata or content changes). See ctime_darwin.go and
|
|
||||||
// ctime_linux.go in the snapshot package for extraction details.
|
|
||||||
CTime time.Time
|
CTime time.Time
|
||||||
Size int64
|
Size int64
|
||||||
Mode uint32
|
Mode uint32
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package snapshot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getCTime extracts the file creation time (birth time) from os.FileInfo.
|
|
||||||
//
|
|
||||||
// On macOS (Darwin), this returns the birth time (Birthtimespec) from the
|
|
||||||
// underlying syscall.Stat_t. macOS HFS+ and APFS filesystems natively track
|
|
||||||
// file creation time, making this a true "created at" timestamp.
|
|
||||||
//
|
|
||||||
// Falls back to modification time if the underlying Sys() data is not a
|
|
||||||
// *syscall.Stat_t (e.g. when using in-memory filesystems for testing).
|
|
||||||
func getCTime(info os.FileInfo) time.Time {
|
|
||||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
|
||||||
if !ok {
|
|
||||||
return info.ModTime()
|
|
||||||
}
|
|
||||||
return time.Unix(stat.Birthtimespec.Sec, stat.Birthtimespec.Nsec).UTC()
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package snapshot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getCTime extracts the inode change time (ctime) from os.FileInfo.
|
|
||||||
//
|
|
||||||
// On Linux, this returns the inode change time (Ctim) from the underlying
|
|
||||||
// syscall.Stat_t. Linux ctime is updated whenever file metadata (permissions,
|
|
||||||
// ownership, link count) or content changes. It is NOT the file creation
|
|
||||||
// (birth) time.
|
|
||||||
//
|
|
||||||
// Note: Linux ext4 (kernel 4.11+) and btrfs do track birth time via the
|
|
||||||
// statx() syscall, but this is not exposed through Go's os.FileInfo.Sys().
|
|
||||||
// The inode change time is the best available approximation through standard
|
|
||||||
// Go APIs.
|
|
||||||
//
|
|
||||||
// Falls back to modification time if the underlying Sys() data is not a
|
|
||||||
// *syscall.Stat_t (e.g. when using in-memory filesystems for testing).
|
|
||||||
func getCTime(info os.FileInfo) time.Time {
|
|
||||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
|
||||||
if !ok {
|
|
||||||
return info.ModTime()
|
|
||||||
}
|
|
||||||
return time.Unix(stat.Ctim.Sec, stat.Ctim.Nsec).UTC()
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//go:build !darwin && !linux
|
|
||||||
|
|
||||||
package snapshot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getCTime returns the file's modification time as a fallback on unsupported
|
|
||||||
// platforms. See ctime_darwin.go and ctime_linux.go for platform-specific
|
|
||||||
// implementations that extract actual ctime/birth time from syscall data.
|
|
||||||
func getCTime(info os.FileInfo) time.Time {
|
|
||||||
return info.ModTime()
|
|
||||||
}
|
|
||||||
@@ -728,7 +728,7 @@ func (s *Scanner) checkFileInMemory(path string, info os.FileInfo, knownFiles ma
|
|||||||
Path: types.FilePath(path),
|
Path: types.FilePath(path),
|
||||||
SourcePath: types.SourcePath(s.currentSourcePath), // Store source directory for restore path stripping
|
SourcePath: types.SourcePath(s.currentSourcePath), // Store source directory for restore path stripping
|
||||||
MTime: info.ModTime(),
|
MTime: info.ModTime(),
|
||||||
CTime: getCTime(info),
|
CTime: info.ModTime(), // afero doesn't provide ctime
|
||||||
Size: info.Size(),
|
Size: info.Size(),
|
||||||
Mode: uint32(info.Mode()),
|
Mode: uint32(info.Mode()),
|
||||||
UID: uid,
|
UID: uid,
|
||||||
|
|||||||
@@ -802,7 +802,7 @@ func (v *Vaultik) syncWithRemote() error {
|
|||||||
snapshotIDStr := snapshot.ID.String()
|
snapshotIDStr := snapshot.ID.String()
|
||||||
if !remoteSnapshots[snapshotIDStr] {
|
if !remoteSnapshots[snapshotIDStr] {
|
||||||
log.Info("Removing local snapshot not found in remote", "snapshot_id", snapshot.ID)
|
log.Info("Removing local snapshot not found in remote", "snapshot_id", snapshot.ID)
|
||||||
if err := v.Repositories.Snapshots.Delete(v.ctx, snapshotIDStr); err != nil {
|
if err := v.deleteSnapshotFromLocalDB(snapshotIDStr); err != nil {
|
||||||
log.Error("Failed to delete local snapshot", "snapshot_id", snapshot.ID, "error", err)
|
log.Error("Failed to delete local snapshot", "snapshot_id", snapshot.ID, "error", err)
|
||||||
} else {
|
} else {
|
||||||
removedCount++
|
removedCount++
|
||||||
|
|||||||
Reference in New Issue
Block a user