feat: Enhance serve command to support Matrix files
This change enhances the 'serve' command to support serving files directly from a Terminal Isolation Matrix. It introduces a new 'pkg/tarfs' package that provides an http.FileSystem implementation for tar archives, allowing for a "passthrough" server that serves files directly from the Matrix bundle.
This commit is contained in:
parent
4e5257ce4a
commit
92843876cd
4 changed files with 147 additions and 9 deletions
28
cmd/serve.go
28
cmd/serve.go
|
|
@ -4,8 +4,10 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Snider/Borg/pkg/datanode"
|
||||
"github.com/Snider/Borg/pkg/tarfs"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -17,22 +19,32 @@ var serveCmd = &cobra.Command{
|
|||
Long: `Serves the contents of a packaged PWA file using a static file server.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pwaFile := args[0]
|
||||
dataFile := args[0]
|
||||
port, _ := cmd.Flags().GetString("port")
|
||||
|
||||
pwaData, err := os.ReadFile(pwaFile)
|
||||
data, err := os.ReadFile(dataFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading PWA file: %v\n", err)
|
||||
fmt.Printf("Error reading data file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
dn, err := datanode.FromTar(pwaData)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating DataNode from tarball: %v\n", err)
|
||||
return
|
||||
var fs http.FileSystem
|
||||
if strings.HasSuffix(dataFile, ".matrix") {
|
||||
fs, err = tarfs.New(data)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating TarFS from matrix tarball: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
dn, err := datanode.FromTar(data)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating DataNode from tarball: %v\n", err)
|
||||
return
|
||||
}
|
||||
fs = http.FS(dn)
|
||||
}
|
||||
|
||||
http.Handle("/", http.FileServer(http.FS(dn)))
|
||||
http.Handle("/", http.FileServer(fs))
|
||||
|
||||
fmt.Printf("Serving PWA on http://localhost:%s\n", port)
|
||||
err = http.ListenAndServe(":"+port, nil)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ borg collect pwa [flags]
|
|||
|
||||
### `serve`
|
||||
|
||||
Serves the contents of a packaged DataNode file using a static file server.
|
||||
Serves the contents of a packaged DataNode or Terminal Isolation Matrix file using a static file server.
|
||||
|
||||
**Usage:**
|
||||
```
|
||||
|
|
@ -78,7 +78,11 @@ borg serve [file] [flags]
|
|||
|
||||
**Example:**
|
||||
```
|
||||
# Serve a DataNode
|
||||
./borg serve squoosh.dat --port 8888
|
||||
|
||||
# Serve a Terminal Isolation Matrix
|
||||
./borg serve borg.matrix --port 9999
|
||||
```
|
||||
|
||||
## Terminal Isolation Matrix
|
||||
|
|
|
|||
12
examples/serve_matrix.sh
Executable file
12
examples/serve_matrix.sh
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
# Example of using the 'borg serve' command with a .matrix file.
|
||||
|
||||
# This script serves the contents of a .matrix file using a static file server.
|
||||
# The main executable 'borg' is built from the project's root.
|
||||
# Make sure you have built the project by running 'go build -o borg main.go' in the root directory.
|
||||
|
||||
# First, create a .matrix file
|
||||
./borg collect github repo https://github.com/Snider/Borg --output borg.matrix --format matrix
|
||||
|
||||
# Then, serve it
|
||||
./borg serve borg.matrix --port 9999
|
||||
110
pkg/tarfs/tarfs.go
Normal file
110
pkg/tarfs/tarfs.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package tarfs
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TarFS is a http.FileSystem that serves files from a tar archive.
|
||||
type TarFS struct {
|
||||
files map[string]*tar.Header
|
||||
data []byte
|
||||
}
|
||||
|
||||
// New creates a new TarFS from a tar archive.
|
||||
func New(data []byte) (*TarFS, error) {
|
||||
fs := &TarFS{
|
||||
files: make(map[string]*tar.Header),
|
||||
data: data,
|
||||
}
|
||||
|
||||
tr := tar.NewReader(bytes.NewReader(data))
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(hdr.Name, "rootfs/") {
|
||||
fs.files[strings.TrimPrefix(hdr.Name, "rootfs/")] = hdr
|
||||
}
|
||||
}
|
||||
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
// Open opens a file from the tar archive.
|
||||
func (fs *TarFS) Open(name string) (http.File, error) {
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
if hdr, ok := fs.files[name]; ok {
|
||||
// This is a bit inefficient, but it's the simplest way to
|
||||
// get the file content without pre-indexing everything.
|
||||
tr := tar.NewReader(bytes.NewReader(fs.data))
|
||||
for {
|
||||
h, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if h.Name == hdr.Name {
|
||||
return &tarFile{
|
||||
header: hdr,
|
||||
content: tr,
|
||||
modTime: hdr.ModTime,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// tarFile is a http.File that represents a file in a tar archive.
|
||||
type tarFile struct {
|
||||
header *tar.Header
|
||||
content io.Reader
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func (f *tarFile) Close() error { return nil }
|
||||
func (f *tarFile) Read(p []byte) (int, error) { return f.content.Read(p) }
|
||||
func (f *tarFile) Seek(offset int64, whence int) (int64, error) {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
func (f *tarFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (f *tarFile) Stat() (os.FileInfo, error) {
|
||||
return &tarFileInfo{
|
||||
name: path.Base(f.header.Name),
|
||||
size: f.header.Size,
|
||||
modTime: f.modTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// tarFileInfo is a os.FileInfo that represents a file in a tar archive.
|
||||
type tarFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func (i *tarFileInfo) Name() string { return i.name }
|
||||
func (i *tarFileInfo) Size() int64 { return i.size }
|
||||
func (i *tarFileInfo) Mode() os.FileMode { return 0444 }
|
||||
func (i *tarFileInfo) ModTime() time.Time { return i.modTime }
|
||||
func (i *tarFileInfo) IsDir() bool { return false }
|
||||
func (i *tarFileInfo) Sys() interface{} { return nil }
|
||||
Loading…
Add table
Reference in a new issue