Files
gluetun/internal/configuration/settings/storage.go
T
Quentin McGaw 25f67cd170 refactor(storage): new storage file structure
- new directory structure containing manifest.json and one json file per provider, by default.
- the manifest.json file can specify a filepath for each vpn provider
- each vpn provider json data file can contain the `"preferred": true` field to enforce it is used even if outdated, unless there is a version mismatch
- `STORAGE_SERVERS_DIRECTORY_PATH` replaces `STORAGE_FILEPATH` (which is now a migration source only). It sets the directory where server manifest and per-provider JSON files are stored (default: `/gluetun/servers/`).
- First-run migration: On startup, gluetun checks for the old /gluetun/servers.json file; if found and no new manifest exists, it automatically migrates all data to /gluetun/servers/ directory structure
- Silent fallback: If legacy file isn't found, uses the new directory path normally
- Legacy cleanup: After successful migration, attempts to remove the old fat JSON file (logs warning only if removal fails, e.g., read-only bind mounts)

Co-authored-by: Copilot <copilot@github.com>
2026-04-27 02:47:30 +00:00

88 lines
2.6 KiB
Go

package settings
import (
"fmt"
"path/filepath"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree"
)
// Storage contains settings to configure the storage.
type Storage struct {
// ServersPath is the path to the servers files directory.
// An empty string disables on-disk storage.
ServersPath *string
// LegacyServersFilepath is the legacy "fat" JSON filepath to migrate from.
// TODO v4: remove
LegacyServersFilepath *string
}
func (s Storage) validate() (err error) {
if *s.ServersPath != "" { // optional
_, err := filepath.Abs(*s.ServersPath)
if err != nil {
return fmt.Errorf("servers path is not valid: %w", err)
}
}
if *s.LegacyServersFilepath != "" {
_, err := filepath.Abs(*s.LegacyServersFilepath)
if err != nil {
return fmt.Errorf("legacy servers filepath is not valid: %w", err)
}
}
return nil
}
func (s *Storage) copy() (copied Storage) {
return Storage{
ServersPath: gosettings.CopyPointer(s.ServersPath),
LegacyServersFilepath: gosettings.CopyPointer(s.LegacyServersFilepath),
}
}
func (s *Storage) overrideWith(other Storage) {
s.ServersPath = gosettings.OverrideWithPointer(s.ServersPath, other.ServersPath)
s.LegacyServersFilepath = gosettings.OverrideWithPointer(s.LegacyServersFilepath, other.LegacyServersFilepath)
}
func (s *Storage) SetDefaults() {
const defaultServersPath = "/gluetun/servers/"
s.ServersPath = gosettings.DefaultPointer(s.ServersPath, defaultServersPath)
s.LegacyServersFilepath = gosettings.DefaultPointer(s.LegacyServersFilepath, constants.ServersDataLegacy)
}
func (s Storage) String() string {
return s.toLinesNode().String()
}
func (s Storage) toLinesNode() (node *gotree.Node) {
if *s.ServersPath == "" {
return gotree.New("Storage settings: disabled")
}
node = gotree.New("Storage settings:")
node.Appendf("Servers directory path: %s", *s.ServersPath)
if *s.LegacyServersFilepath != constants.ServersDataLegacy {
node.Appendf("Legacy servers filepath: %s", *s.LegacyServersFilepath)
}
return node
}
func (s *Storage) Read(r *reader.Reader) (err error) {
// Retro-compatibility:
// TODO v4: remove support for STORAGE_FILEPATH
filePath := r.Get("STORAGE_FILEPATH", reader.AcceptEmpty(true), reader.IsRetro("STORAGE_SERVERS_DIRECTORY_PATH"))
if filePath != nil {
s.LegacyServersFilepath = filePath
if *filePath == "" {
s.ServersPath = ptrTo("") // skip disk operations
}
}
if s.ServersPath == nil {
s.ServersPath = r.Get("STORAGE_SERVERS_DIRECTORY_PATH", reader.AcceptEmpty(true))
}
return nil
}