Compare commits
1 Commits
fix/sync-s
...
a53203d60d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a53203d60d |
46
README.md
46
README.md
@@ -194,8 +194,11 @@ 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, and ownership (ownership requires root)
|
* Preserves file permissions, timestamps (mtime), 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
|
||||||
@@ -207,6 +210,42 @@ 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
|
||||||
@@ -247,11 +286,14 @@ 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,6 +17,10 @@ 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
|
||||||
|
|||||||
23
internal/snapshot/ctime_darwin.go
Normal file
23
internal/snapshot/ctime_darwin.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
29
internal/snapshot/ctime_linux.go
Normal file
29
internal/snapshot/ctime_linux.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
15
internal/snapshot/ctime_other.go
Normal file
15
internal/snapshot/ctime_other.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//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: info.ModTime(), // afero doesn't provide ctime
|
CTime: getCTime(info),
|
||||||
Size: info.Size(),
|
Size: info.Size(),
|
||||||
Mode: uint32(info.Mode()),
|
Mode: uint32(info.Mode()),
|
||||||
UID: uid,
|
UID: uid,
|
||||||
|
|||||||
Reference in New Issue
Block a user