Add jukebox_maker web app v1.0

Go web application for filling USB drives with media files.
Runs in Docker on Unraid with /media, /mnt/usb, /config volumes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-23 21:33:43 +03:00
parent eb3f84ea31
commit 29f3ad9576
24 changed files with 1901 additions and 0 deletions

89
internal/config/config.go Normal file
View File

@@ -0,0 +1,89 @@
package config
import (
"encoding/json"
"errors"
"os"
"path/filepath"
)
type OverwriteMode string
type FileSelectMode string
const (
OverwriteSkip OverwriteMode = "skip"
OverwriteDelete OverwriteMode = "delete"
SelectNew FileSelectMode = "new"
SelectAll FileSelectMode = "all"
)
type SourceFolder struct {
Path string `json:"path"`
Enabled bool `json:"enabled"`
}
type Config struct {
ReserveFreeGB float64 `json:"reserve_free_gb"`
Sources []SourceFolder `json:"sources"`
OverwriteMode OverwriteMode `json:"overwrite_mode"`
FileSelectMode FileSelectMode `json:"file_select_mode"`
AutoCopy bool `json:"auto_copy"`
}
func defaults() Config {
return Config{
ReserveFreeGB: 2.0,
OverwriteMode: OverwriteSkip,
FileSelectMode: SelectNew,
AutoCopy: false,
}
}
func Load(path string) (*Config, error) {
data, err := os.ReadFile(path)
if errors.Is(err, os.ErrNotExist) {
cfg := defaults()
return &cfg, nil
}
if err != nil {
return nil, err
}
cfg := defaults()
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
func Save(path string, cfg *Config) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
tmp := path + ".tmp"
if err := os.WriteFile(tmp, data, 0o644); err != nil {
return err
}
return os.Rename(tmp, path)
}
func (c *Config) Validate() error {
if c.ReserveFreeGB < 0 {
return errors.New("reserve_free_gb must be >= 0")
}
switch c.OverwriteMode {
case OverwriteSkip, OverwriteDelete:
default:
return errors.New("overwrite_mode must be 'skip' or 'delete'")
}
switch c.FileSelectMode {
case SelectNew, SelectAll:
default:
return errors.New("file_select_mode must be 'new' or 'all'")
}
return nil
}