Skip to content

Commit 62b2fa1

Browse files
committed
Merge branch 'master' into feature/s3fs-path-style-and-bucket-check
2 parents 3075290 + 159179c commit 62b2fa1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+8934
-2228
lines changed

.github/workflows/daily-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ jobs:
172172
file: ./agfs-server/Dockerfile
173173
push: true
174174
tags: |
175+
${{ secrets.DOCKERHUB_USERNAME }}/agfs:latest
175176
${{ secrets.DOCKERHUB_USERNAME }}/agfs:nightly
176177
${{ secrets.DOCKERHUB_USERNAME }}/agfs:nightly-${{ steps.version.outputs.date }}
177178
${{ secrets.DOCKERHUB_USERNAME }}/agfs:nightly-${{ steps.version.outputs.short_sha }}

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,19 @@ curl -fsSL https://raw.githubusercontent.com/c4pt0r/agfs/master/install.sh | sh
3838
Or via Docker:
3939

4040
```bash
41-
docker pull c4pt0r/agfs-server:latest
41+
docker pull c4pt0r/agfs:latest
42+
43+
# Run the server (HTTP API only)
44+
docker run -p 8080:8080 -e SKIP_FUSE_MOUNT=true c4pt0r/agfs:latest
45+
46+
# On Linux, you can enable FUSE mounting with additional privileges
47+
docker run -p 8080:8080 \
48+
--device /dev/fuse \
49+
--cap-add SYS_ADMIN \
50+
--security-opt apparmor:unconfined \
51+
c4pt0r/agfs:latest
52+
53+
# Note: FUSE mounting in Docker is not supported on macOS
4254
```
4355

4456
Connect using agfs-shell:
@@ -212,3 +224,4 @@ See [task_loop.py](./agfs-mcp/demos/task_loop.py) for a complete example.
212224
- [agfs-server](./agfs-server/README.md) - Server configuration and plugin development
213225
- [agfs-shell](./agfs-shell/README.md) - Interactive shell client
214226
- [agfs-fuse](./agfs-fuse/README.md) - FUSE filesystem mount (Linux)
227+

agfs-fuse/pkg/fusefs/file.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/hanwen/go-fuse/v2/fs"
88
"github.com/hanwen/go-fuse/v2/fuse"
9+
log "github.com/sirupsen/logrus"
910
)
1011

1112
// AGFSFileHandle represents an open file handle
@@ -32,15 +33,19 @@ func (fh *AGFSFileHandle) Read(ctx context.Context, dest []byte, off int64) (fus
3233

3334
// Write writes data to the file
3435
func (fh *AGFSFileHandle) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) {
36+
path := fh.node.getPath()
37+
log.Debugf("[file] Write called: path=%s, len=%d, off=%d, handle=%d", path, len(data), off, fh.handle)
38+
3539
n, err := fh.node.root.handles.Write(fh.handle, data, off)
3640
if err != nil {
41+
log.Errorf("[file] Write failed: path=%s, err=%v", path, err)
3742
return 0, syscall.EIO
3843
}
3944

4045
// Invalidate metadata cache since file size may have changed
41-
path := fh.node.getPath()
4246
fh.node.root.metaCache.Invalidate(path)
4347

48+
log.Debugf("[file] Write success: path=%s, written=%d", path, n)
4449
return uint32(n), 0
4550
}
4651

agfs-fuse/pkg/fusefs/handles.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,16 @@ func (hm *HandleManager) Write(fuseHandle uint64, data []byte, offset int64) (in
408408
path := info.path
409409
hm.mu.Unlock()
410410

411+
log.Debugf("[handles] Local handle write: path=%s, len=%d, offset=%d", path, len(data), offset)
412+
411413
// Send directly to server
412414
_, err := hm.client.Write(path, data)
413415
if err != nil {
416+
log.Errorf("[handles] Write failed for %s: %v", path, err)
414417
return 0, fmt.Errorf("failed to write to server: %w", err)
415418
}
416419

420+
log.Debugf("[handles] Write success for %s: %d bytes", path, len(data))
417421
return len(data), nil
418422
}
419423

agfs-fuse/pkg/fusefs/node.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
agfs "github.com/c4pt0r/agfs/agfs-sdk/go"
99
"github.com/hanwen/go-fuse/v2/fs"
1010
"github.com/hanwen/go-fuse/v2/fuse"
11+
log "github.com/sirupsen/logrus"
1112
)
1213

1314
// AGFSNode represents a file or directory node
@@ -250,25 +251,34 @@ func (n *AGFSNode) Create(ctx context.Context, name string, flags uint32, mode u
250251
path := n.getPath()
251252
childPath := filepath.Join(path, name)
252253

254+
log.Debugf("[node] Create called: path=%s, name=%s, childPath=%s", path, name, childPath)
255+
253256
// Create the file
254257
err := n.root.client.Create(childPath)
255258
if err != nil {
259+
log.Errorf("[node] Create failed for %s: %v", childPath, err)
256260
return nil, nil, 0, syscall.EIO
257261
}
258262

263+
log.Debugf("[node] Create succeeded, opening handle for %s", childPath)
264+
259265
// Invalidate caches
260266
n.root.invalidateCache(childPath)
261267

262268
// Open the file with the requested flags
263269
openFlags := convertOpenFlags(flags)
264270
fuseHandle, err := n.root.handles.Open(childPath, openFlags, mode)
265271
if err != nil {
272+
log.Errorf("[node] Open handle failed for %s: %v", childPath, err)
266273
return nil, nil, 0, syscall.EIO
267274
}
268275

276+
log.Debugf("[node] Handle opened: %d for %s", fuseHandle, childPath)
277+
269278
// Fetch file info
270279
info, err := n.root.client.Stat(childPath)
271280
if err != nil {
281+
log.Errorf("[node] Stat failed for %s: %v", childPath, err)
272282
n.root.handles.Close(fuseHandle)
273283
return nil, nil, 0, syscall.EIO
274284
}

agfs-sdk/python/pyagfs/client.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ def get_plugins_info(self) -> List[dict]:
566566
except Exception as e:
567567
self._handle_request_error(e)
568568

569-
def grep(self, path: str, pattern: str, recursive: bool = False, case_insensitive: bool = False, stream: bool = False):
569+
def grep(self, path: str, pattern: str, recursive: bool = False, case_insensitive: bool = False, stream: bool = False, limit: int = 0):
570570
"""Search for a pattern in files using regular expressions
571571
572572
Args:
@@ -575,6 +575,7 @@ def grep(self, path: str, pattern: str, recursive: bool = False, case_insensitiv
575575
recursive: Whether to search recursively in directories (default: False)
576576
case_insensitive: Whether to perform case-insensitive matching (default: False)
577577
stream: Whether to stream results as NDJSON (default: False)
578+
limit: Maximum number of results to return (0 means default, for vector search defaults to 10)
578579
579580
Returns:
580581
If stream=False: Dict with 'matches' (list of match objects) and 'count'
@@ -593,15 +594,19 @@ def grep(self, path: str, pattern: str, recursive: bool = False, case_insensitiv
593594
... print(f"{item['file']}:{item['line']}: {item['content']}")
594595
"""
595596
try:
597+
request_body = {
598+
"path": path,
599+
"pattern": pattern,
600+
"recursive": recursive,
601+
"case_insensitive": case_insensitive,
602+
"stream": stream
603+
}
604+
if limit > 0:
605+
request_body["limit"] = limit
606+
596607
response = self.session.post(
597608
f"{self.api_base}/grep",
598-
json={
599-
"path": path,
600-
"pattern": pattern,
601-
"recursive": recursive,
602-
"case_insensitive": case_insensitive,
603-
"stream": stream
604-
},
609+
json=request_body,
605610
timeout=None if stream else self.timeout,
606611
stream=stream
607612
)

agfs-server/Dockerfile

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ FROM golang:1.25-alpine AS builder
33

44
# Install build dependencies
55
RUN apk add --no-cache git make gcc musl-dev sqlite-dev python3 py3-pip npm \
6-
autoconf automake libtool python3-dev jq-dev
6+
autoconf automake libtool python3-dev jq-dev fuse3-dev
77

88
# Install uv for Python package management
99
RUN pip3 install --break-system-packages uv
@@ -29,11 +29,16 @@ WORKDIR /build-shell
2929
COPY agfs-shell .
3030
RUN python3 build.py
3131

32+
# Build agfs-fuse
33+
WORKDIR /build-fuse
34+
COPY agfs-fuse .
35+
RUN go build -o agfs-fuse ./cmd/agfs-fuse
36+
3237
# Runtime stage
3338
FROM alpine:latest
3439

35-
# Install runtime dependencies (including Python 3 for agfs-shell)
36-
RUN apk add --no-cache ca-certificates sqlite-libs python3 jq oniguruma
40+
# Install runtime dependencies (including Python 3 for agfs-shell and fuse3 for agfs-fuse)
41+
RUN apk add --no-cache ca-certificates sqlite-libs python3 jq oniguruma fuse3 wget
3742

3843
# Create app directory
3944
WORKDIR /app
@@ -48,13 +53,20 @@ COPY --from=builder /build/config.example.yaml /config.yaml
4853
# Copy agfs-shell portable distribution
4954
COPY --from=builder /build-shell/dist/agfs-shell-portable /usr/local/agfs-shell
5055
RUN ln -s /usr/local/agfs-shell/agfs-shell /usr/local/bin/agfs-shell
56+
RUN ln -s /usr/local/agfs-shell/agfs-shell /usr/local/bin/agfs
57+
58+
# Copy agfs-fuse binary
59+
COPY --from=builder /build-fuse/agfs-fuse /usr/local/bin/agfs-fuse
60+
61+
# Copy docker entrypoint script
62+
COPY agfs-server/docker-entrypoint.sh /docker-entrypoint.sh
63+
RUN chmod +x /docker-entrypoint.sh
5164

52-
# Create directory for localfs mount
53-
RUN mkdir -p /data
65+
# Create directories
66+
RUN mkdir -p /data /mnt/agfs
5467

5568
# Expose default port
5669
EXPOSE 8080
5770

58-
# Run the server
59-
ENTRYPOINT ["./agfs-server"]
60-
CMD ["-c", "/config.yaml"]
71+
# Run agfs-server with agfs-fuse mount
72+
ENTRYPOINT ["/docker-entrypoint.sh"]

agfs-server/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,22 @@ The easiest way to get started is using Docker:
2020

2121
1. **Pull the image**:
2222
```bash
23-
docker pull c4pt0r/agfs-server:latest
23+
docker pull c4pt0r/agfs:latest
2424
```
2525

2626
2. **Run the server with port mapping**:
2727
```bash
2828
# Basic run - expose port 8080 to host
29-
docker run -d -p 8080:8080 --name agfs-server c4pt0r/agfs-server:latest
29+
docker run -d -p 8080:8080 --name agfs-server c4pt0r/agfs:latest
3030
3131
# With custom port mapping (host:container)
32-
docker run -d -p 9000:8080 --name agfs-server c4pt0r/agfs-server:latest
32+
docker run -d -p 9000:8080 --name agfs-server c4pt0r/agfs:latest
3333
3434
# With data persistence (mount /data directory)
35-
docker run -d -p 8080:8080 -v $(pwd)/data:/data --name agfs-server c4pt0r/agfs-server:latest
35+
docker run -d -p 8080:8080 -v $(pwd)/data:/data --name agfs-server c4pt0r/agfs:latest
3636
3737
# With custom configuration
38-
docker run -d -p 8080:8080 -v $(pwd)/config.yaml:/config.yaml --name agfs-server c4pt0r/agfs-server:latest
38+
docker run -d -p 8080:8080 -v $(pwd)/config.yaml:/config.yaml --name agfs-server c4pt0r/agfs:latest
3939
```
4040

4141
3. **Using agfs-shell inside the container**:

agfs-server/cmd/server/main.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/c4pt0r/agfs/agfs-server/pkg/mountablefs"
1414
"github.com/c4pt0r/agfs/agfs-server/pkg/plugin"
1515
"github.com/c4pt0r/agfs/agfs-server/pkg/plugin/api"
16+
"github.com/c4pt0r/agfs/agfs-server/pkg/plugins/devfs"
1617
"github.com/c4pt0r/agfs/agfs-server/pkg/plugins/gptfs"
1718
"github.com/c4pt0r/agfs/agfs-server/pkg/plugins/heartbeatfs"
1819
"github.com/c4pt0r/agfs/agfs-server/pkg/plugins/hellofs"
@@ -28,6 +29,7 @@ import (
2829
"github.com/c4pt0r/agfs/agfs-server/pkg/plugins/sqlfs2"
2930
"github.com/c4pt0r/agfs/agfs-server/pkg/plugins/streamfs"
3031
"github.com/c4pt0r/agfs/agfs-server/pkg/plugins/streamrotatefs"
32+
"github.com/c4pt0r/agfs/agfs-server/pkg/plugins/vectorfs"
3133
log "github.com/sirupsen/logrus"
3234
)
3335

@@ -43,6 +45,7 @@ type PluginFactory func() plugin.ServicePlugin
4345

4446
// availablePlugins maps plugin names to their factory functions
4547
var availablePlugins = map[string]PluginFactory{
48+
"devfs": func() plugin.ServicePlugin { return devfs.NewDevFSPlugin() },
4649
"serverinfofs": func() plugin.ServicePlugin { return serverinfofs.NewServerInfoFSPlugin() },
4750
"memfs": func() plugin.ServicePlugin { return memfs.NewMemFSPlugin() },
4851
"queuefs": func() plugin.ServicePlugin { return queuefs.NewQueueFSPlugin() },
@@ -58,6 +61,7 @@ var availablePlugins = map[string]PluginFactory{
5861
"sqlfs2": func() plugin.ServicePlugin { return sqlfs2.NewSQLFS2Plugin() },
5962
"localfs": func() plugin.ServicePlugin { return localfs.NewLocalFSPlugin() },
6063
"gptfs": func() plugin.ServicePlugin { return gptfs.NewGptfs() },
64+
"vectorfs": func() plugin.ServicePlugin { return vectorfs.NewVectorFSPlugin() },
6165
}
6266

6367
const sampleConfig = `# AGFS Server Configuration File
@@ -329,6 +333,22 @@ func main() {
329333
}
330334
}
331335

336+
// Mount DevFS by default (always enabled)
337+
log.Info("Mounting default DevFS at /dev...")
338+
devfsPlugin := devfs.NewDevFSPlugin()
339+
devfsConfig := map[string]interface{}{
340+
"mount_path": "/dev",
341+
}
342+
if err := devfsPlugin.Validate(devfsConfig); err != nil {
343+
log.Errorf("Failed to validate DevFS: %v", err)
344+
} else if err := devfsPlugin.Initialize(devfsConfig); err != nil {
345+
log.Errorf("Failed to initialize DevFS: %v", err)
346+
} else if err := mfs.Mount("/dev", devfsPlugin); err != nil {
347+
log.Errorf("Failed to mount DevFS at /dev: %v", err)
348+
} else {
349+
log.Info("DevFS mounted successfully at /dev")
350+
}
351+
332352
// Mount all enabled plugins
333353
log.Info("Mounting plugin filesytems...")
334354
for pluginName, pluginCfg := range cfg.Plugins {

agfs-server/config.example.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,41 @@ plugins:
150150
# secret_access_key: secret
151151
# prefix: agfs/ # Optional: all keys will be prefixed with "agfs/"
152152
#
153+
# # ============================================================================
154+
# # VectorFS - Document Vector Search (AI-Powered Semantic Search)
155+
# # ============================================================================
156+
# # VectorFS provides semantic search capabilities for documents using:
157+
# # - S3 for scalable document storage
158+
# # - TiDB Cloud vector index for fast similarity search
159+
# # - OpenAI embeddings for vector representations
160+
# #
161+
# # See config.vectorfs.example.yaml for detailed configuration
162+
# #
163+
# vectorfs:
164+
# enabled: false
165+
# path: /vectorfs
166+
# config:
167+
# # S3 Configuration
168+
# s3_bucket: "your-bucket-name"
169+
# s3_key_prefix: "vectorfs"
170+
# s3_region: "us-west-1"
171+
# s3_access_key: "your-access-key"
172+
# s3_secret_key: "your-secret-key"
173+
#
174+
# # TiDB Cloud Configuration
175+
# tidb_dsn: "user:pass@tcp(host:4000)/db?tls=true"
176+
#
177+
# # OpenAI Configuration
178+
# openai_api_key: "sk-..."
179+
# embedding_model: "text-embedding-3-small"
180+
# embedding_dim: 1536
181+
#
182+
# # Performance tuning (optional)
183+
# chunk_size: 512
184+
# chunk_overlap: 50
185+
# index_workers: 4
186+
#
187+
153188
# # ============================================================================
154189
# # HTTPFS - HTTP File Server (Multiple Instances)
155190
# # ============================================================================
@@ -231,6 +266,10 @@ plugins:
231266
# ├── sqlfs_tidb/ (database-backed filesystem - TiDB)
232267
# ├── httagfs-queue (virtual status file - httagfs serving queuefs)
233268
# ├── httagfs-s3 (virtual status file - httagfs serving S3)
269+
# ├── vectorfs/ (AI-powered semantic document search)
270+
# │ ├── README (VectorFS documentation)
271+
# │ └── <namespace>/
272+
# │ └── docs/ (indexed documents with semantic search)
234273
# ├── s3fs/
235274
# │ └── aws/ (AWS S3 storage)
236275
# └── proxyfs/
@@ -287,3 +326,25 @@ plugins:
287326
# agfs mount httagfs /docs agfs_path=/memfs/docs http_port=8001
288327
# agfs mount httagfs /images agfs_path=/memfs/images http_port=8002
289328
# agfs mount httagfs /s3-public agfs_path=/s3fs/aws/public http_port=8003
329+
#
330+
# VectorFS - AI-Powered Semantic Search (when enabled):
331+
# # Create a namespace
332+
# agfs mkdir /vectorfs/my_docs
333+
#
334+
# # Add documents (auto-indexed with embeddings)
335+
# agfs write /vectorfs/my_docs/docs/guide.txt "Kubernetes deployment guide..."
336+
# agfs write /vectorfs/my_docs/docs/tutorials/docker.txt "Docker tutorial..."
337+
#
338+
# # Copy entire folders (async indexing, no timeout)
339+
# agfs cp -r /s3fs/aws/docs /vectorfs/my_docs/docs/imported
340+
#
341+
# # Read original documents
342+
# agfs cat /vectorfs/my_docs/docs/guide.txt
343+
# agfs cat /vectorfs/my_docs/docs/tutorials/docker.txt
344+
#
345+
# # Semantic search (vector similarity, returns relevant chunks)
346+
# agfs grep "how to deploy containers" /vectorfs/my_docs/docs
347+
# # Returns results with similarity scores
348+
#
349+
# # Use agfs-shell's fsgrep for better formatted output
350+
# fsgrep -r "deployment strategies" /vectorfs/my_docs/docs

0 commit comments

Comments
 (0)