Files

79 lines
2.4 KiB
Go
Raw Permalink Normal View History

2026-10-15 14:29:19 +03:30
// SPDX-License-Identifier: MIT
// Package state persists the runtime snapshot prx needs to describe and undo
// the currently applied routing configuration.
package state
import (
"encoding/json"
"errors"
"fmt"
"os"
"time"
"prx/internal/fsx"
)
// State is the on-disk snapshot.
type State struct {
Enabled bool `json:"enabled"`
WANInterface string `json:"wan_interface"`
Gateway4 string `json:"gateway_v4,omitempty"`
Gateway6 string `json:"gateway_v6,omitempty"`
Mark uint32 `json:"mark"`
TableV4 int `json:"table_v4"`
TableV6 int `json:"table_v6"`
PriorityV4 int `json:"priority_v4"`
PriorityV6 int `json:"priority_v6"`
BypassTableV4 int `json:"bypass_table_v4"`
BypassTableV6 int `json:"bypass_table_v6"`
BypassPrioV4 int `json:"bypass_priority_v4"`
BypassPrioV6 int `json:"bypass_priority_v6"`
BypassCIDRs []string `json:"bypass_cidrs,omitempty"`
EngineManaged bool `json:"engine_managed"`
EngineWasRunning bool `json:"engine_was_running"`
EnabledAt time.Time `json:"enabled_at"`
Version string `json:"version,omitempty"`
}
// Load reads the state. A missing file yields a zero (disabled) state.
//
// The returned state is never nil, even alongside an error, so callers cannot
// dereference a nil pointer after ignoring a read failure.
func Load(path string) (*State, error) {
raw, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return &State{}, nil
}
return &State{}, fmt.Errorf("read state %s: %w", path, err)
}
var s State
if err := json.Unmarshal(raw, &s); err != nil {
return &State{}, fmt.Errorf("parse state %s: %w", path, err)
}
return &s, nil
}
// Save writes the state atomically, creating the parent directory.
func Save(path string, s *State) error {
raw, err := json.MarshalIndent(s, "", " ")
if err != nil {
return fmt.Errorf("encode state: %w", err)
}
raw = append(raw, '\n')
if err := fsx.WriteFileAtomic(path, raw, 0o644); err != nil {
return fmt.Errorf("write state %s: %w", path, err)
}
return nil
}
// Remove deletes the state file. A missing file is not an error.
func Remove(path string) error {
err := os.Remove(path)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
return nil
}