247 lines
6.3 KiB
Markdown
247 lines
6.3 KiB
Markdown
# Detail: genisoimage → go-diskfs
|
|
|
|
## 1. `internal/isobuilder/builder.go` — new file
|
|
|
|
```go
|
|
package isobuilder
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
diskfs "github.com/diskfs/go-diskfs"
|
|
"github.com/diskfs/go-diskfs/disk"
|
|
"github.com/diskfs/go-diskfs/filesystem"
|
|
"github.com/diskfs/go-diskfs/filesystem/iso9660"
|
|
)
|
|
|
|
// BuildFromDirectory creates an ISO 9660 image from a source directory.
|
|
// Equivalent to: genisoimage -iso-level 4 -r -J -V <label> -o <isoPath> <srcDir>
|
|
func BuildFromDirectory(srcDir, isoPath, volumeLabel string) error {
|
|
// Step 1: Calculate total size (sum of all files + 10% overhead)
|
|
totalSize, err := dirSize(srcDir)
|
|
if err != nil {
|
|
return fmt.Errorf("calculate directory size: %w", err)
|
|
}
|
|
|
|
// Step 2: Create disk image
|
|
mydisk, err := diskfs.Create(isoPath, totalSize, diskfs.SectorSizeDefault)
|
|
if err != nil {
|
|
return fmt.Errorf("create disk image: %w", err)
|
|
}
|
|
mydisk.LogicalBlocksize = 2048
|
|
|
|
// Step 3: Create ISO 9660 filesystem
|
|
fs, err := mydisk.CreateFilesystem(disk.FilesystemSpec{
|
|
Partition: 0,
|
|
FSType: filesystem.TypeISO9660,
|
|
VolumeLabel: volumeLabel,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("create ISO filesystem: %w", err)
|
|
}
|
|
defer fs.Close()
|
|
|
|
// Step 4: Walk source dir and copy files
|
|
err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
relPath, err := filepath.Rel(srcDir, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if relPath == "." {
|
|
return nil // skip root
|
|
}
|
|
|
|
if info.IsDir() {
|
|
if err := fs.Mkdir(relPath); err != nil {
|
|
return fmt.Errorf("mkdir %q: %w", relPath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Regular file — copy contents
|
|
rw, err := fs.OpenFile(relPath, os.O_CREATE|os.O_RDWR)
|
|
if err != nil {
|
|
return fmt.Errorf("open file %q: %w", relPath, err)
|
|
}
|
|
defer rw.Close()
|
|
|
|
srcFile, err := os.Open(path)
|
|
if err != nil {
|
|
return fmt.Errorf("open source %q: %w", path, err)
|
|
}
|
|
defer srcFile.Close()
|
|
|
|
if _, err := io.Copy(rw, srcFile); err != nil {
|
|
return fmt.Errorf("copy %q: %w", relPath, err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("walk source directory: %w", err)
|
|
}
|
|
|
|
// Step 5: Finalize with Rock Ridge + Joliet
|
|
iso, ok := fs.(*iso9660.FileSystem)
|
|
if !ok {
|
|
return fmt.Errorf("not an ISO 9660 filesystem")
|
|
}
|
|
if err := iso.Finalize(iso9660.FinalizeOptions{
|
|
RockRidge: true,
|
|
Joliet: true,
|
|
DeepDirectories: true,
|
|
VolumeIdentifier: volumeLabel,
|
|
}); err != nil {
|
|
return fmt.Errorf("finalize ISO: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// dirSize calculates total size of all files in a directory tree,
|
|
// with 10% overhead margin for ISO metadata.
|
|
func dirSize(dir string) (int64, error) {
|
|
var total int64
|
|
err := filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
total += info.Size()
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
// Add 10% overhead for ISO 9660 metadata
|
|
total = total + total/10
|
|
// Minimum 10MB (some PACS studies are small)
|
|
if total < 10*1024*1024 {
|
|
total = 10 * 1024 * 1024
|
|
}
|
|
// Round up to next 2048-byte sector
|
|
if total%2048 != 0 {
|
|
total = total + (2048 - total%2048)
|
|
}
|
|
return total, nil
|
|
}
|
|
```
|
|
|
|
## 2. `internal/config/config.go` — remove Tools field
|
|
|
|
**Current (line ~31):**
|
|
```go
|
|
type Config struct {
|
|
Server ServerConfig `yaml:"server"`
|
|
Auth AuthConfig `yaml:"auth"`
|
|
DCMTK DCMTKConfig `yaml:"dcmtk"`
|
|
Tools ToolsConfig `yaml:"tools"`
|
|
...
|
|
}
|
|
```
|
|
|
|
**Remove `Tools ToolsConfig` field and entire `ToolsConfig` struct.**
|
|
|
|
**Current validation (line ~82):**
|
|
```go
|
|
if c.Tools.Genisoimage == "" {
|
|
return fmt.Errorf("tools.genisoimage is required")
|
|
}
|
|
```
|
|
|
|
**Remove the above lines.**
|
|
|
|
## 3. `config.example.yaml` — remove `tools:` block
|
|
|
|
Remove lines:
|
|
```yaml
|
|
tools:
|
|
genisoimage: "/usr/bin/genisoimage"
|
|
```
|
|
|
|
## 4. `internal/service/iso.go` — replace genisoimage call
|
|
|
|
**Current in GenerateISO() (line ~68-78):**
|
|
```go
|
|
isoPath := filepath.Join(s.cfg.ISO.TempDir, isoName)
|
|
|
|
exitCode, stdout, stderr, err := dicom.RunGenISOImage(ctx, s.cfg.Tools.Genisoimage, isoPath, tempDir)
|
|
if err != nil || exitCode != 0 {
|
|
cleanup()
|
|
os.Remove(isoPath)
|
|
return nil, fmt.Errorf("genisoimage failed (exit %d): %s (stderr: %s)", exitCode, err, stderr)
|
|
}
|
|
_ = stdout
|
|
|
|
slog.Info("ISO created",
|
|
"path", isoPath,
|
|
"accession", accessionNumber,
|
|
)
|
|
```
|
|
|
|
**Replace with:**
|
|
```go
|
|
isoPath := filepath.Join(s.cfg.ISO.TempDir, isoName)
|
|
|
|
if err := isobuilder.BuildFromDirectory(tempDir, isoPath, "DICOM"); err != nil {
|
|
cleanup()
|
|
os.Remove(isoPath)
|
|
return nil, fmt.Errorf("ISO creation failed: %w", err)
|
|
}
|
|
|
|
slog.Info("ISO created",
|
|
"path", isoPath,
|
|
"accession", accessionNumber,
|
|
)
|
|
```
|
|
|
|
**Same replacement in GenerateISOMultiple() — find the second `dicom.RunGenISOImage` call and replace identically.**
|
|
|
|
Also add import: `"mkiso-server/internal/isobuilder"`
|
|
|
|
## 5. `internal/handler/health.go` — remove genisoimage from deps
|
|
|
|
**Current (line ~19):**
|
|
```go
|
|
deps := []struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
Status string `json:"status"`
|
|
}{
|
|
{"storescp", cfg.DCMTK.Storescp, ""},
|
|
{"movescu", cfg.DCMTK.Movescu, ""},
|
|
{"storescu", cfg.DCMTK.Storescu, ""},
|
|
{"genisoimage", cfg.Tools.Genisoimage, ""},
|
|
}
|
|
```
|
|
|
|
**Replace with (remove genisoimage entry, remove `cfg.Tools` reference):**
|
|
```go
|
|
deps := []struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
Status string `json:"status"`
|
|
}{
|
|
{"storescp", cfg.DCMTK.Storescp, ""},
|
|
{"movescu", cfg.DCMTK.Movescu, ""},
|
|
{"storescu", cfg.DCMTK.Storescu, ""},
|
|
}
|
|
```
|
|
|
|
## 6. `pkg/dicom/command.go` — remove `RunGenISOImage`
|
|
|
|
Delete the entire `RunGenISOImage` function (lines ~150-180).
|
|
|
|
## 7. `go.mod` — after `go get`
|
|
|
|
Will add: `github.com/diskfs/go-diskfs v1.9.3` plus its transitive deps.
|