# Replace genisoimage with go-diskfs (pure Go ISO creation)

## Why

`genisoimage` is the only remaining external binary dependency after the dcmtk migration.
Replacing it with `github.com/diskfs/go-diskfs` (pure Go, MIT, 644⭐, v1.9.3) makes the
mkiso-server binary fully self-contained — zero external binaries beyond dcmtk storescp/movescu/storescu.

## What

Replace `pkg/dicom/command.go`'s `RunGenISOImage()` (which shells out to genisoimage)
with pure Go ISO creation using go-diskfs. This affects:

| Current | New |
|---------|-----|
| `service/iso.go` calls `dicom.RunGenISOImage()` | Calls `isobuilder.BuildFromDirectory()` |
| `pkg/dicom/command.go` has `RunGenISOImage()` | Remove it |
| `config.go` has `Tools.Genisoimage` + validation | Remove field + validation |
| `config.example.yaml` has `tools.genisoimage` | Remove |
| `handler/health.go` checks genisoimage binary | Remove check |

## go-diskfs API reference

```go
import (
    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"
)

type FinalizeOptions struct {
    RockRidge        bool      // Rock Ridge extensions (long names, perms)
    Joliet           bool      // Joliet (UCS-2 names for Windows)
    DeepDirectories  bool      // Allow dirs deeper than 8 levels
    ElTorito         *ElTorito
    VolumeIdentifier string    // Volume label, default "ISOIMAGE"
    PublisherIdentifier string
}
```

### ISO creation pattern (from go-diskfs examples/create-iso-from-folder/)

```go
// 1. Calculate total size of directory
folderSize := dirSize(srcDir)

// 2. Create disk image file
mydisk, err := diskfs.Create(isoPath, folderSize, 2048)
mydisk.LogicalBlocksize = 2048

// 3. Create ISO9660 filesystem
fs, err := mydisk.CreateFilesystem(disk.FilesystemSpec{
    Partition:   0,
    FSType:      filesystem.TypeISO9660,
    VolumeLabel: "DICOM",
})

// 4. Walk source dir, copy files
filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
    relPath, _ := filepath.Rel(srcDir, path)
    if info.IsDir() {
        fs.Mkdir(relPath)
    } else {
        rw, _ := fs.OpenFile(relPath, os.O_CREATE|os.O_RDWR)
        io.Copy(rw, file)
        rw.Close()
    }
})

// 5. Finalize with Rock Ridge + Joliet
iso := fs.(*iso9660.FileSystem)
iso.Finalize(iso9660.FinalizeOptions{
    RockRidge:       true,
    Joliet:          true,
    DeepDirectories: true,
    VolumeIdentifier: "DICOM",
})
fs.Close()
```

### Equivalent genisoimage flags to go-diskfs options

| genisoimage flag | go-diskfs FinalizeOptions |
|-----------------|--------------------------|
| `-iso-level 4` | `DeepDirectories: true` |
| `-r` (Rock Ridge) | `RockRidge: true` |
| `-V DICOM` | `VolumeIdentifier: "DICOM"` |
| `-allow-multidot` | Implicit via Rock Ridge |
| `-allow-lowercase` | Implicit via Rock Ridge |
| `-allow-leading-dots` | Implicit via Rock Ridge |
| `-J` (Joliet) | `Joliet: true` |

## Implementation order

- [ ] **1. Add go-diskfs dependency** — `go get github.com/diskfs/go-diskfs@v1.9.3`
- [ ] **2. Create `internal/isobuilder/builder.go`** — `BuildFromDirectory(srcDir, isoPath, volumeLabel string) error`
  - `dirSize()` helper (walk dir, sum file sizes + 10% overhead margin)
  - Walk again to create dirs + copy files
  - Finalize with RockRidge + Joliet + DeepDirectories + VolumeIdentifier
- [ ] **3. Update `internal/config/config.go`**
  - Remove `ToolsConfig.Genisoimage` field
  - Remove validation `c.Tools.Genisoimage == ""`
  - Remove `Tools` from Config struct entirely if empty (keep struct, remove field)
- [ ] **4. Update `config.example.yaml`** — remove `tools.genisoimage` section
- [ ] **5. Update `internal/service/iso.go`**
  - Replace both `dicom.RunGenISOImage(...)` calls with `isobuilder.BuildFromDirectory(tempDir, isoPath, "DICOM")`
  - Remove unused `dicom` import alias (if no longer needed in scope — it's still used for `countFiles` elsewhere)
- [ ] **6. Update `internal/handler/health.go`**
  - Remove genisoimage from the dependency check array
- [ ] **7. Remove `RunGenISOImage` from `pkg/dicom/command.go`**
  - Delete the entire function
- [ ] **8. Build and test**
  - `go build ./...` must pass
  - `go vet ./...` must pass
  - Start server, verify `/api/health` no longer shows genisoimage
  - Verify ISO endpoint still returns proper errors (PACS unreachable)
