feat(state): add runtime state snapshot
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user