diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..692f41c --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,39 @@ +name: Go + +on: + push: + branches: [ "main", "dev" ] + pull_request: + branches: [ "main", "dev" ] + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: write + +jobs: + + build: + if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && !github.event.pull_request.draft) + runs-on: ubuntu-latest + strategy: + matrix: + go: ['1.23', '1.24', '1.25', '1.26'] + os: [ 'linux', 'windows', 'darwin' ] + arch: [ 'amd64', 'arm64' ] + outputs: + commit-hash: ${{ steps.get_commit.outputs.commit }} + + steps: + - uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: ${{ matrix.go }} + + - name: Build + env: + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + run: go build -v ./... + diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..dce6d35 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,37 @@ +name: golangci-lint + +on: + push: + branches: [ "main", "dev" ] + pull_request: + branches: [ "main", "dev" ] + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: 1.26 + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v9 + with: + version: latest + skip-cache: true + skip-save-cache: true + + - name: Upload lint results + uses: actions/upload-artifact@v7 + if: failure() + with: + name: golangci-lint-results + path: golangci-lint-report.txt + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7e7bf7b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + +permissions: + contents: write + +jobs: + + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: 1.26 + + - name: Release + uses: goreleaser/goreleaser-action@v7 + with: + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/README.md b/README.md index 4396004..7b65330 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ It is built on top of [`vbktoolkit`](https://github.com/GoToolSharing/vbktoolkit - Search text in files (`grep`) - List embedded virtual disk files (`disks`) - Auto-detect guest partitions from embedded virtual disks (`volumes`, `use`) +- Guest filesystem support: `NTFS`, `EXT4`, and `XFS` - Interactive tab-completion for commands and paths in shell mode - JSON output mode for automation (`--json`) - Stable exit codes for common error categories diff --git a/go.mod b/go.mod index 2ffa811..db637a6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/GoToolSharing/vbkview -go 1.22 +go 1.23.0 require ( github.com/GoToolSharing/vbktoolkit v0.0.0-20260420090906-925abf074c28 diff --git a/internal/vbkshell/advanced.go b/internal/vbkshell/advanced.go index a5d544f..6c8d244 100644 --- a/internal/vbkshell/advanced.go +++ b/internal/vbkshell/advanced.go @@ -95,7 +95,7 @@ func (s *Shell) ExtractWithOptions(src, dst string, opts ExtractOptions) (GetRes if err != nil { return GetResult{}, err } - defer out.Close() + defer closeWithWarning(outPath, out) if opts.Resume { if st, statErr := out.Stat(); statErr == nil { @@ -154,7 +154,7 @@ func (s *Shell) ExtractWithOptions(src, dst string, opts ExtractOptions) (GetRes if err != nil { return GetResult{}, err } - defer in.Close() + defer closeWithWarning(target, in) res := GetResult{SourcePath: src, ResolvedPath: target, OutputPath: outPath} startOffset := int64(0) @@ -185,7 +185,7 @@ func (s *Shell) ExtractWithOptions(src, dst string, opts ExtractOptions) (GetRes if err != nil { return GetResult{}, err } - defer out.Close() + defer closeWithWarning(outPath, out) written, err := io.CopyBuffer(out, in, make([]byte, 1024*1024)) if err != nil { @@ -250,7 +250,7 @@ func (s *Shell) CatData(src string, limit int64, forceBase64 bool) (CatResult, e if err != nil { return CatResult{}, err } - defer stream.Close() + defer closeWithWarning(target, stream) var data []byte if limit >= 0 { @@ -459,7 +459,7 @@ func (s *Shell) Grep(pattern, start string, opts GrepOptions) ([]GrepMatch, erro if err != nil { return nil } - defer stream.Close() + defer closeWithWarning(p, stream) reader := io.Reader(stream) if opts.MaxBytes > 0 { @@ -541,7 +541,7 @@ func sha256File(filePath string) (string, error) { if err != nil { return "", err } - defer f.Close() + defer closeWithWarning(filePath, f) h := sha256.New() if _, err := io.Copy(h, f); err != nil { diff --git a/internal/vbkshell/close.go b/internal/vbkshell/close.go new file mode 100644 index 0000000..f582b06 --- /dev/null +++ b/internal/vbkshell/close.go @@ -0,0 +1,16 @@ +package vbkshell + +import ( + "fmt" + "io" + "os" +) + +func closeWithWarning(name string, closer io.Closer) { + if closer == nil { + return + } + if err := closer.Close(); err != nil { + fmt.Fprintf(os.Stderr, "warning: failed to close %s: %v\n", name, err) + } +} diff --git a/internal/vbkshell/shell.go b/internal/vbkshell/shell.go index 1cb1de7..cb6482e 100644 --- a/internal/vbkshell/shell.go +++ b/internal/vbkshell/shell.go @@ -99,7 +99,7 @@ func (s *Shell) Run() error { s.cmdHelp() lineReader := liner.NewLiner() - defer lineReader.Close() + defer closeWithWarning("line reader", lineReader) lineReader.SetCtrlCAborts(true) lineReader.SetCompleter(s.completeInput)