Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/inception-mount/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.test
/inception-mount
43 changes: 43 additions & 0 deletions src/inception-mount/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# inception-mount

An **owned, FUSE-free** FileSystem seam for mounting inception spaces from user
land — the way a Docker volume driver abstracts backing storage, but
proof-carrying (capability lease + hash-chained receipts + warrant-typed
content). See [docs/ADR-0001](docs/ADR-0001-inception-mount.md).

Not macFUSE. Not any kernel-mount dependency inside pods. One `go-billy`
FileSystem seam (`fs.InceptionFS`), served two ways:

- **Agent / pod face** — link the VFS in-process. No `mount()` syscall ⇒ no
privilege ⇒ runs inside a restricted-PSA `sovereign-runtime` pod.
- **Human / userland face** — served as userspace **NFSv3 over loopback**; the
OS-native NFS client mounts it. FSKit (macOS 26+) is the native successor.

Governance lives in the seam, so it is identical on both faces: every op is gated
by a capability lease (**fail-closed**) and leaves a **hash-chained receipt**;
`unmount ≡ revocation`.

## Layout

- `fs/governance.go` — `Lease`, fail-closed `Membrane`, hash-chained `ReceiptLog`.
- `fs/inceptionfs.go` — `InceptionFS`: decorates any `billy.Filesystem` backend.
- `cmd/inception-mount` — serve a space as userspace NFSv3 on loopback.

## Run

```bash
go test ./... # both faces + governance (wire round-trip skips unless root)

# serve a local dir as a governed, read-only inception space over loopback NFS:
go run ./cmd/inception-mount -dir /path/to/space -space demo-space
# then, on the human's own machine (their sudo grants the mount — no kext, no FUSE):
# sudo mount -o vers=3,tcp,port=22049,mountport=22049,noowners,rw -t nfs 127.0.0.1:/ /path/to/mnt
```

## Status

Spike. Proven: privilege-free in-process VFS with fail-closed governance +
verified receipt chain; governed NFSv3 server stands up unprivileged on loopback;
full NFS-client wire read + denied write (root-gated). Next: real backends
(trit-pack / HellGraph / zot), FSKit module, write-back consistency across shared
replicas.
99 changes: 99 additions & 0 deletions src/inception-mount/backend/backend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package backend_test

import (
"bytes"
"os"
"path/filepath"
"testing"
"time"

bk "github.com/SociOS-Linux/SourceOS/src/inception-mount/backend"
)

func ids(vs []bk.VersionMeta) []string {
out := make([]string, len(vs))
for i, v := range vs {
out[i] = v.ID
}
return out
}

func sample(now time.Time) []bk.VersionMeta {
return []bk.VersionMeta{
{Version: bk.Version{ID: "a"}, Created: now.Add(-4 * time.Hour)},
{Version: bk.Version{ID: "b"}, Created: now.Add(-3 * time.Hour)},
{Version: bk.Version{ID: "c"}, Created: now.Add(-2 * time.Hour)},
{Version: bk.Version{ID: "d"}, Created: now.Add(-1 * time.Hour)},
}
}

func TestRetention_KeepLast(t *testing.T) {
now := time.Now()
keep, prune := bk.RetentionPolicy{KeepLast: 2}.Plan(sample(now), now)
if got := ids(keep); len(got) != 2 || got[0] != "d" || got[1] != "c" {
t.Fatalf("keep = %v, want [d c]", got)
}
if got := ids(prune); len(got) != 2 {
t.Fatalf("prune = %v, want 2", got)
}
}

func TestRetention_KeepSince(t *testing.T) {
now := time.Now()
keep, prune := bk.RetentionPolicy{KeepSince: 150 * time.Minute}.Plan(sample(now), now)
// newer than 2.5h → c (-2h) and d (-1h); a,b pruned
if got := ids(keep); len(got) != 2 {
t.Fatalf("keep = %v, want 2 (c,d)", got)
}
if len(prune) != 2 {
t.Fatalf("prune = %d, want 2", len(prune))
}
}

func TestRetention_ZeroKeepsAll(t *testing.T) {
now := time.Now()
_, prune := bk.RetentionPolicy{}.Plan(sample(now), now)
if len(prune) != 0 {
t.Fatalf("zero policy must never prune, got %v", ids(prune))
}
}

func TestApply_DevPruner_DeletesPruneSet(t *testing.T) {
root := t.TempDir()
var plan []bk.VersionMeta
for _, id := range []string{"a", "b"} {
d := filepath.Join(root, id)
os.MkdirAll(d, 0o755)
plan = append(plan, bk.VersionMeta{Version: bk.Version{ID: id, Ref: d}})
}
done, err := bk.Apply(bk.DevPruner{}, plan)
if err != nil || len(done) != 2 {
t.Fatalf("apply: done=%d err=%v", len(done), err)
}
for _, id := range []string{"a", "b"} {
if _, err := os.Stat(filepath.Join(root, id)); !os.IsNotExist(err) {
t.Fatalf("version %s should be pruned", id)
}
}
}

func TestDevReplicator_RoundTrip(t *testing.T) {
src := t.TempDir()
os.MkdirAll(filepath.Join(src, "sub"), 0o755)
os.WriteFile(filepath.Join(src, "twin.ttl"), []byte("<urn:twin> a hdt:FHIRResource .\n"), 0o444)
os.WriteFile(filepath.Join(src, "sub", "finding.ttl"), []byte("<urn:finding> a hdt:Observation .\n"), 0o444)

v := bk.Version{ID: "x", Ref: src, Kind: "dev"}
var buf bytes.Buffer
if err := (bk.DevReplicator{}).Send("", v, &buf); err != nil {
t.Fatalf("send: %v", err)
}
dst := t.TempDir()
if err := (bk.DevReplicator{}).Receive(&buf, dst); err != nil {
t.Fatalf("receive: %v", err)
}
got, err := os.ReadFile(filepath.Join(dst, "sub", "finding.ttl"))
if err != nil || !bytes.Contains(got, []byte("Observation")) {
t.Fatalf("replicated content missing: %q err=%v", got, err)
}
}
37 changes: 37 additions & 0 deletions src/inception-mount/backend/btrfs_daemon_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//go:build linux

package backend

import (
"fmt"
"io"

"github.com/dennwc/btrfs"
)

// BtrfsPruner deletes a snapshot subvolume (retention). Privileged; OS daemon only.
type BtrfsPruner struct{}

func (BtrfsPruner) Prune(v Version) error {
if v.Ref == "" {
return fmt.Errorf("btrfs prune: version has no subvolume ref")
}
return btrfs.DeleteSubVolume(v.Ref)
}

// BtrfsReplicator is the managed-network face: `btrfs send`/`receive`. Send emits
// version v as a delta from parent (a parent snapshot path; empty = full send).
type BtrfsReplicator struct{}

func (BtrfsReplicator) Kind() string { return "btrfs" }

func (BtrfsReplicator) Send(parent string, v Version, w io.Writer) error {
if v.Ref == "" {
return fmt.Errorf("btrfs send: version has no subvolume ref")
}
return btrfs.Send(w, parent, v.Ref)
}

func (BtrfsReplicator) Receive(r io.Reader, dstDir string) error {
return btrfs.Receive(r, dstDir)
}
29 changes: 29 additions & 0 deletions src/inception-mount/backend/btrfs_daemon_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//go:build !linux

package backend

import (
"fmt"
"io"
)

// Non-Linux stubs so the daemon capabilities compile everywhere; dev uses
// DevPruner / DevReplicator. The btrfs impls require Linux + CAP_SYS_ADMIN.

type BtrfsPruner struct{}

func (BtrfsPruner) Prune(v Version) error {
return fmt.Errorf("btrfs pruner requires linux; use DevPruner off-Linux")
}

type BtrfsReplicator struct{}

func (BtrfsReplicator) Kind() string { return "btrfs" }

func (BtrfsReplicator) Send(parent string, v Version, w io.Writer) error {
return fmt.Errorf("btrfs replicator requires linux; use DevReplicator off-Linux")
}

func (BtrfsReplicator) Receive(r io.Reader, dstDir string) error {
return fmt.Errorf("btrfs replicator requires linux; use DevReplicator off-Linux")
}
87 changes: 87 additions & 0 deletions src/inception-mount/backend/btrfs_e2e_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//go:build linux

package backend_test

import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"

bk "github.com/SociOS-Linux/SourceOS/src/inception-mount/backend"
"github.com/dennwc/btrfs"
)

// TestBtrfsE2E is the runtime proof of the Linux substrate: it drives the REAL
// BtrfsSnapshotter / BtrfsReplicator / BtrfsPruner against an actual btrfs mount
// (snapshot → read-only + UUID:gen id → send/receive replicate → prune). It skips
// unless pointed at a btrfs mount as root, so `go test ./...` stays portable.
//
// INCEPTION_BTRFS_ROOT=/mnt/space (a btrfs filesystem) sudo -E go test -run BtrfsE2E
func TestBtrfsE2E(t *testing.T) {
root := os.Getenv("INCEPTION_BTRFS_ROOT")
if root == "" {
t.Skip("set INCEPTION_BTRFS_ROOT to a btrfs mount to run the runtime proof")
}
if os.Geteuid() != 0 {
t.Skip("btrfs e2e needs root (subvolume ops require CAP_SYS_ADMIN)")
}

space := filepath.Join(root, "space")
if err := btrfs.CreateSubVolume(space); err != nil {
t.Fatalf("create subvolume: %v", err)
}
snapDir := filepath.Join(root, "snaps")
if err := os.MkdirAll(snapDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(space, "twin.ttl"),
[]byte("<urn:twin> a hdt:FHIRResource .\n"), 0o644); err != nil {
t.Fatal(err)
}

// SNAPSHOT — real btrfs read-only snapshot, id = UUID:generation
v, err := bk.NewBtrfsSnapshotter(space, snapDir).Snapshot("e2e")
if err != nil {
t.Fatalf("snapshot: %v", err)
}
if !strings.Contains(v.ID, ":") {
t.Fatalf("expected UUID:generation id, got %q", v.ID)
}
if ro, _ := btrfs.IsReadOnly(v.Ref); !ro {
t.Fatalf("snapshot %s must be read-only", v.Ref)
}
if _, err := os.Stat(filepath.Join(v.Ref, "twin.ttl")); err != nil {
t.Fatalf("snapshot missing seeded file: %v", err)
}
t.Logf("snapshot ok: id=%s ref=%s", v.ID, v.Ref)

// REPLICATE — real btrfs send | receive (the managed-network face)
var stream bytes.Buffer
if err := (bk.BtrfsReplicator{}).Send("", v, &stream); err != nil {
t.Fatalf("btrfs send: %v", err)
}
sent := stream.Len() // capture before Receive drains the buffer
recvDir := filepath.Join(root, "received")
if err := os.MkdirAll(recvDir, 0o755); err != nil {
t.Fatal(err)
}
if err := (bk.BtrfsReplicator{}).Receive(&stream, recvDir); err != nil {
t.Fatalf("btrfs receive: %v", err)
}
got, err := os.ReadFile(filepath.Join(recvDir, filepath.Base(v.Ref), "twin.ttl"))
if err != nil || !bytes.Contains(got, []byte("FHIRResource")) {
t.Fatalf("replicated content missing: %q err=%v", got, err)
}
t.Logf("send/receive ok: %d-byte send-stream replicated, content verified", sent)

// PRUNE — real subvolume delete (retention)
if err := (bk.BtrfsPruner{}).Prune(v); err != nil {
t.Fatalf("btrfs prune: %v", err)
}
if _, err := os.Stat(v.Ref); !os.IsNotExist(err) {
t.Fatalf("pruned snapshot %s should be gone", v.Ref)
}
t.Log("prune ok: snapshot subvolume deleted")
}
87 changes: 87 additions & 0 deletions src/inception-mount/backend/btrfs_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//go:build linux

package backend

import (
"fmt"
"os"
"path/filepath"
"time"

"github.com/dennwc/btrfs"
)

// BtrfsSnapshotter is the production Linux Snapshotter, on the OWNED go btrfs
// library (dennwc/btrfs, Apache-2.0 — pure-Go ioctls, no shell-out to a distro
// binary). A commit is a read-only `btrfs` snapshot: O(1), COW-cheap, immutable.
// Subvolume ops need CAP_SYS_ADMIN, so this runs in the owned OS-level mounter
// daemon, never an unprivileged agent pod — which is why versioning is an OS
// concern. Cross-node replication is BtrfsReplicator (send/receive).
type BtrfsSnapshotter struct {
subvol string // the space's read-write subvolume (working tree)
snapDir string // directory holding read-only snapshots
}

func NewBtrfsSnapshotter(subvol, snapDir string) *BtrfsSnapshotter {
return &BtrfsSnapshotter{subvol: subvol, snapDir: snapDir}
}

func (b *BtrfsSnapshotter) Kind() string { return "btrfs" }

// List reports the snapshots under snapDir (id = UUID:generation, Created = the
// subvolume's btrfs OTime; falls back to the dir mtime if info can't be read).
func (b *BtrfsSnapshotter) List() ([]VersionMeta, error) {
entries, err := os.ReadDir(b.snapDir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
var out []VersionMeta
for _, e := range entries {
if !e.IsDir() {
continue
}
p := filepath.Join(b.snapDir, e.Name())
created := time.Time{}
if fi, err := e.Info(); err == nil {
created = fi.ModTime()
}
if fs, err := btrfs.Open(p, true); err == nil {
if info, err := fs.SubvolumeByPath(p); err == nil && info != nil {
created = info.OTime
}
fs.Close()
}
out = append(out, VersionMeta{
Version: Version{ID: snapshotID(p), Ref: p, Kind: "btrfs"},
Created: created,
})
}
return out, nil
}

func (b *BtrfsSnapshotter) Snapshot(purpose string) (Version, error) {
dest := filepath.Join(b.snapDir, fmt.Sprintf("v-%d", time.Now().UTC().UnixNano()))
if err := btrfs.SnapshotSubVolume(b.subvol, dest, true); err != nil {
return Version{}, fmt.Errorf("btrfs snapshot %s -> %s: %w", b.subvol, dest, err)
}
return Version{ID: snapshotID(dest), Ref: dest, Kind: "btrfs"}, nil
}

// snapshotID returns the snapshot's btrfs UUID:generation — a globally-unique,
// immutable identity the receipt chain binds (bind-at-capture). Falls back to the
// dest path if the subvolume info can't be read.
func snapshotID(dest string) string {
fs, err := btrfs.Open(dest, true)
if err != nil {
return dest
}
defer fs.Close()
info, err := fs.SubvolumeByPath(dest)
if err != nil || info == nil {
return dest
}
return fmt.Sprintf("%s:%d", info.UUID.String(), info.CTransID)
}
Loading
Loading